From 4651ccbe6973c3f550978e8a0fa2e276d804e3e5 Mon Sep 17 00:00:00 2001 From: Antone Barbaud Date: Tue, 9 Jun 2026 16:06:19 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20document=20MessagesService=20=E2=80=94?= =?UTF-8?q?=20single=20per-plugin=20YAML,=20defaults=20in=20jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit features.md: new section 8 "Service de messages" with the single-file model, two-layer in-memory (jar defaults + user file), API examples, template-override mechanism, and override-the-impl extension point. Bootstrap section renumbered to 9. decisions.md: new decision logging the choice of MessagesService design (YAML, single per-plugin file, in-jar fallback for new keys, template priority from the game plugin's own resource, programmatic set() for dynamic cases). README.md: feature list mentions externalized messages; diagrams index adds messages-class-diagram.puml. setup.md: new "Fichier messages" section explaining the file location, how to seed it from a game plugin's bundled template, and the API for hot reload / extras / programmatic set(). New diagram: docs/diagrams/messages-class-diagram.puml — MessagesService interface, YamlMessagesService impl, link to CRCore, with explanatory note on the two-source merge and template fallback at first boot. Co-Authored-By: Claude Opus 4.7 --- docs/README.md | 5 + docs/decisions.md | 30 ++++++ docs/diagrams/messages-class-diagram.puml | 63 ++++++++++++ docs/features.md | 116 +++++++++++++++++++++- docs/setup.md | 29 ++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 docs/diagrams/messages-class-diagram.puml diff --git a/docs/README.md b/docs/README.md index e70dac3..0a1fc32 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,10 @@ d'initialisation côté plugin de jeu : - **Persistance SQLite** — wrapper `Database` + `TableBuilder` fluide, repositories SQLite write-through, table custom en 2 lignes pour les plugins downstream. +- **Messages externalisés** — `MessagesService` charge un seul fichier + YAML `-messages.yml` dans le dataFolder du plugin de jeu, + avec defaults CR-Core en fallback. L'admin édite un seul fichier, + placeholders nommés, codes couleur `&` natifs. - **Bootstrap unique** — `new CRCore(this).enable()` dans le `onEnable()` du plugin de jeu, et tout est branché. @@ -50,6 +54,7 @@ d'initialisation côté plugin de jeu : | [builtin-commands-diagram.puml](diagrams/builtin-commands-diagram.puml) | Classe | Arbre des commandes `/core team ...` | | [events-diagram.puml](diagrams/events-diagram.puml) | Classe | Évènements Bukkit team + player | | [database-diagram.puml](diagrams/database-diagram.puml) | Classe | Wrapper SQLite + table builder | +| [messages-class-diagram.puml](diagrams/messages-class-diagram.puml) | Classe | Service de messages YAML | | [bootstrap-sequence.puml](diagrams/bootstrap-sequence.puml) | Séquence | `CRCore.enable()` côté plugin de jeu | ## Conventions diff --git a/docs/decisions.md b/docs/decisions.md index ffefcfb..5204421 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -367,6 +367,36 @@ Format léger : une décision = un titre + contexte + choix + raison. bases existantes, ALTER TABLE manuel ou suppression du fichier (les bases d'event sont jetables). +## 2026-06-09 — `MessagesService` : YAML externalisable, un seul fichier par plugin + +- **Choix** : nouveau module `fr.luc.crcore.message` avec une interface + `MessagesService` et une impl `YamlMessagesService`. Toutes les chaînes + utilisateur des commandes built-in passent par ce service. +- **Modèle « un seul fichier par plugin »** : + - Defaults CR-Core embarqués dans le jar à + `resources/crcore-messages.yml` — **jamais écrits sur disque**, juste + chargés en mémoire comme couche de fallback. + - Fichier user unique : + `/-messages.yml`. Auto-créé + au premier `enable()` à partir du template du plugin de jeu s'il en + bundle un sous le même nom, sinon à partir des defaults CR-Core. + - Lecture : le fichier user écrase les defaults sur les mêmes clés ; + une clé manquante retombe automatiquement sur le default CR-Core (donc + une future release CR-Core qui ajoute une clé marche sans intervention + admin). +- **Pourquoi un seul fichier** : (1) UX admin — il édite un fichier, pas + deux ; (2) le plugin de jeu peut pré-remplir le template avec ses + overrides + ses propres messages en bundlant simplement son fichier + homonyme dans les ressources ; (3) zéro maintenance pour les clés + inchangées — elles restent en jar. +- **Substitution** : placeholders `{name}` style, varargs key/value, + codes couleur `&` traduits automatiquement. +- **Override de l'impl** : `CRCore.buildMessagesService()` protected, + surchargeable pour passer à une autre source (DB, microservice, etc.). +- **Pas de programmatique-only** : le service supporte `set(key, template)` + en mémoire pour des cas dynamiques, mais le mode principal reste le + YAML pour l'éditabilité par l'admin sans recompile. + ## 2026-06-09 — Toutes les commandes "chef" deviennent admin (révision) - **Révision** de la décision "Refonte permissions + modèle admin/chef/joueur" diff --git a/docs/diagrams/messages-class-diagram.puml b/docs/diagrams/messages-class-diagram.puml new file mode 100644 index 0000000..d26ce25 --- /dev/null +++ b/docs/diagrams/messages-class-diagram.puml @@ -0,0 +1,63 @@ +@startuml messages-class-diagram +title CR-Core — Messages service (class diagram) + +skinparam classAttributeIconSize 0 +hide empty members + +package "fr.luc.crcore.message" { + + interface MessagesService { + + get(key, placeholderPairs...): String + + raw(key): String + + has(key): boolean + + set(key, template): void + + reload(): void + + loadAdditional(resourceName): void + + setApplyColorCodes(enabled): void + + isApplyColorCodes(): boolean + + getUserFile(): File + } + + class YamlMessagesService { + - plugin: JavaPlugin + - defaults: Map ' in-memory (jar resource) + - messages: Map ' effective (defaults + user file) + - userFileName: String ' -messages.yml + - userFile: File + - applyColorCodes: boolean + -- + + YamlMessagesService(plugin: JavaPlugin) + -- + - loadDefaultsFromResource(): void ' charge crcore-messages.yml (jar) + - ensureUserFile(): void ' copie template si absent + - rebuildEffectiveMessages(): void ' defaults + user file + - {static} flatten(section, prefix, out): void + } + + YamlMessagesService ..|> MessagesService +} + +package "fr.luc.crcore" { + class CRCore { + + messages(): MessagesService + # buildMessagesService(): MessagesService ' override point + } + CRCore "1" *-- "1" MessagesService : owns +} + +note bottom of YamlMessagesService + Modèle "un seul fichier par plugin" : + + Sources en mémoire (la 2e écrase la 1ère sur clés communes) : + 1. crcore-messages.yml ← jar (toujours présent, fallback) + 2. -messages.yml ← dataFolder (édité par l'admin) + + Création du fichier user au 1er boot : + Priorité 1 : ressource du plugin de jeu sous le même nom + Priorité 2 : copie des defaults CR-Core + + Placeholders : {name} via varargs key/value + Codes couleur : &a → §a (toggle) +end note + +@enduml diff --git a/docs/features.md b/docs/features.md index d835d8a..5f73ce9 100644 --- a/docs/features.md +++ b/docs/features.md @@ -570,7 +570,121 @@ la hook. --- -## 8. Bootstrap `CRCore` +## 8. Service de messages (`fr.luc.crcore.message`) + +**Statut** : implémenté. Toutes les commandes built-in `/core team ...` passent +par ce service ; plus rien n'est hardcodé dans le code. + +### Modèle « un seul fichier par plugin » + +Au premier démarrage, CR-Core crée **un seul fichier** dans le dataFolder du +plugin de jeu : + +``` +/-messages.yml +``` + +(par exemple `cites-messages.yml` si le plugin s'appelle `Cites`.) C'est *le* +fichier que l'admin du serveur édite. Pas de `crcore-messages.yml` séparé sur +disque — les defaults CR-Core vivent dans le jar et sont chargés en mémoire +comme couche de fallback. + +### Deux couches en mémoire + +| Ordre | Source | Mutabilité | +|---|---|---| +| 1 | `crcore-messages.yml` embarqué dans le jar CR-Core | Read-only, in-memory | +| 2 | `-messages.yml` dans le dataFolder | Lecture du disque | + +La couche 2 écrase la couche 1 sur les clés communes. Une clé manquante dans +le fichier user **retombe automatiquement** sur le default CR-Core. Si une +future release CR-Core ajoute une nouvelle clé, l'admin n'a rien à faire — ça +marche immédiatement. + +### Création du fichier user au premier démarrage + +L'ordre de priorité pour générer le starter file : + +1. Si le plugin de jeu **bundle son propre** `-messages.yml` + dans ses ressources → c'est ce fichier qui devient le template (donc tu peux + pré-remplir avec tes overrides + tes messages perso au build). +2. Sinon → copie des defaults CR-Core comme starter (l'admin voit toutes les + clés CR-Core et peut les éditer). + +### API `MessagesService` + +```java +// Lecture avec placeholders nommés +core.messages().get("team.create.success", + "name", team.getName(), + "tag", team.getTag(), + "visibility", team.getVisibility().name()); +// → "&aÉquipe Wolves [#WOLF] créée (PRIVATE, sans chef)." (codes & déjà traduits) + +// Lecture brute (sans substitution ni couleur) +core.messages().raw("team.create.success"); + +// Ajout/override programmatique en mémoire (non persisté) +core.messages().set("mygame.welcome", "&aBienvenue {player} !"); + +// Charge un fichier YAML additionnel (en plus du fichier user principal) +core.messages().loadAdditional("my-extras.yml"); + +// Hot reload (relit le fichier user, garde les defaults en mémoire) +core.messages().reload(); + +// Toggle codes couleur +core.messages().setApplyColorCodes(true); // défaut + +// Chemin du fichier user (informationnel) +File path = core.messages().getUserFile(); +``` + +### Placeholders + +Format `{name}` avec substitution via varargs paire-par-paire. Codes couleur +Bukkit `&a`, `&c`, `&7`, `&f`, etc. traduits automatiquement en `§…`. + +### Clés manquantes + +Si une clé n'existe ni dans le fichier user ni dans les defaults, +`messages.get(...)` renvoie `[missing: key]` pour rendre visible la clé qu'il +manque (debug facile). + +### Override côté plugin de jeu + +**Option 1 — Bundler son propre template** dans les ressources du plugin de +jeu, fichier nommé `-messages.yml`. À la première +exécution, ce fichier sert de starter dans le dataFolder. + +**Option 2 — Éditer le fichier généré** après premier démarrage. Le YAML +contient déjà les clés CR-Core ; ajoute / modifie / supprime ce que tu veux. + +**Option 3 — Override programmatique** : +```java +core.messages().set("team.create.success", "Custom format for {name}"); +``` + +### Override de l'impl + +`CRCore.buildMessagesService()` est `protected`. Sous-classe CRCore et +redéfinis-la pour une impl custom (ex. messages venant d'une base de données, +d'un microservice de traduction, etc.). + +### Fichier `crcore-messages.yml` — référence + +~25 clés organisées en sections : `common.*` (no-permission, player-only, …), +`team.create.*`, `team.delete.success`, `team.add.*`, etc. Voir le fichier +dans `src/main/resources/crcore-messages.yml` pour la liste complète avec les +placeholders documentés en commentaire. + +### Diagramme + +- Classes : [messages-class-diagram.puml](diagrams/messages-class-diagram.puml) + +--- + +## 9. Bootstrap `CRCore` **Statut** : implémenté. Point d'entrée unique pour les plugins de jeu. diff --git a/docs/setup.md b/docs/setup.md index 6692898..67553e5 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -257,6 +257,35 @@ CitesPlugin/ # dossier IntelliJ (renommer plus t └── PlayerScoreChangeEvent.java ``` +## Fichier messages + +Au premier `enable()`, CR-Core crée : + +``` +/-messages.yml +``` + +(ex. `cites-messages.yml` si ton plugin s'appelle `Cites`) + +Édite ce fichier pour customiser tous les messages des commandes `/core team +...`. Les clés non présentes retombent sur les defaults CR-Core (dans le jar) +— donc tu peux n'inclure que ce que tu veux changer. + +Pour pré-remplir ce fichier avec tes overrides + tes propres messages dès la +première exécution, bundle un fichier au **même nom** dans tes ressources +plugin (`src/main/resources/cites-messages.yml`). Si présent, c'est lui qui +sert de template au lieu des defaults CR-Core. + +API en code : +```java +core.messages().get("team.create.success", + "name", team.getName(), "tag", team.getTag()); + +core.messages().set("mygame.custom", "&aHello {player} !"); +core.messages().loadAdditional("my-extras.yml"); +core.messages().reload(); // hot reload depuis le disque +``` + ## Tables SQLite créées par CR-Core Au premier `enable()`, les tables suivantes sont créées (en `IF NOT EXISTS`) :