Possibilité d'importer un template de monstre depuis le systeme qu'on utilise sur Foundry pour forcer le template. Lors de l'export, le monstre est automatiquement adapté comme ça.
All checks were successful
All checks were successful
à l'import des monstres depuis foundry : on garde la structure originelle utilisée sur foundry également
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
@if (g.folder) {
|
||||
<h2 class="sbl-folder">
|
||||
<lucide-icon [img]="Folder" [size]="14"></lucide-icon>
|
||||
{{ g.folder }}
|
||||
{{ g.folder.split('/').join(' / ') }}
|
||||
<span class="sbl-folder-count">{{ g.enemies.length }}</span>
|
||||
</h2>
|
||||
} @else if (groups.length > 1) {
|
||||
|
||||
@@ -12,4 +12,21 @@
|
||||
</div>
|
||||
|
||||
<app-persona-view [persona]="enemy" [templateFields]="templateFields" [subtitle]="subtitle"></app-persona-view>
|
||||
|
||||
@if (hasFoundryStats) {
|
||||
<section class="foundry-stats">
|
||||
<h2>{{ 'enemyView.foundryStatsTitle' | translate }}</h2>
|
||||
<p class="foundry-stats-hint">{{ 'enemyView.foundryStatsHint' | translate }}</p>
|
||||
<table class="foundry-stats-table">
|
||||
<tbody>
|
||||
@for (s of foundryStatsEntries; track s.key) {
|
||||
<tr>
|
||||
<th>{{ s.key }}</th>
|
||||
<td>{{ s.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,49 @@
|
||||
color: #d8b4fe;
|
||||
}
|
||||
|
||||
// Snapshot des stats importées de Foundry (lecture seule, figé).
|
||||
.foundry-stats {
|
||||
max-width: 1100px;
|
||||
margin: 28px auto 0;
|
||||
padding: 0 32px;
|
||||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
color: #d1a878;
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.foundry-stats-hint {
|
||||
margin: 0 0 0.75rem;
|
||||
color: #9aa0aa;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.foundry-stats-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.82rem;
|
||||
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
width: 45%;
|
||||
color: #9aa0aa;
|
||||
font-weight: 500;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
td { color: #e0e0e0; }
|
||||
}
|
||||
}
|
||||
|
||||
// Pages de Lore liées au PNJ — chips cliquables sous la fiche.
|
||||
.nv-lore-links {
|
||||
max-width: 1100px;
|
||||
|
||||
@@ -77,6 +77,16 @@ export class EnemyViewComponent implements OnInit, OnDestroy {
|
||||
this.paramsSub?.unsubscribe();
|
||||
}
|
||||
|
||||
/** Stats importées de Foundry (snapshot figé), triées pour affichage. */
|
||||
get foundryStatsEntries(): { key: string; value: string }[] {
|
||||
const s = this.enemy?.foundryStats ?? {};
|
||||
return Object.keys(s).sort().map(key => ({ key, value: s[key] }));
|
||||
}
|
||||
|
||||
get hasFoundryStats(): boolean {
|
||||
return this.foundryStatsEntries.length > 0;
|
||||
}
|
||||
|
||||
/** Sous-titre de la fiche : niveau + dossier (« Niveau 8 · Démons »). */
|
||||
get subtitle(): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
@@ -153,6 +153,22 @@
|
||||
(fieldsChange)="npcTemplate = $event">
|
||||
</app-template-fields-editor>
|
||||
|
||||
@if (id) {
|
||||
<div class="structure-import">
|
||||
<button type="button" class="btn-secondary" [disabled]="importingStructure" (click)="structureInput.click()">
|
||||
<lucide-icon [img]="Upload" [size]="14"></lucide-icon>
|
||||
{{ (importingStructure ? 'gameSystemEdit.structureImporting' : 'gameSystemEdit.structureImport') | translate }}
|
||||
</button>
|
||||
<input #structureInput type="file" accept=".json,application/json" hidden (change)="onStructureSelected($event)" />
|
||||
@if (foundryActorType) {
|
||||
<span class="structure-actor-type">{{ 'gameSystemEdit.structureActorType' | translate:{ type: foundryActorType } }}</span>
|
||||
}
|
||||
@if (importStructureError) {
|
||||
<span class="structure-error">{{ importStructureError }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-template-fields-editor
|
||||
[label]="'gameSystemEdit.enemyFieldsLabel' | translate"
|
||||
[hint]="'gameSystemEdit.enemyFieldsHint' | translate"
|
||||
|
||||
@@ -88,6 +88,12 @@ export class GameSystemEditComponent implements OnInit {
|
||||
npcTemplate: TemplateField[] = [];
|
||||
enemyTemplate: TemplateField[] = [];
|
||||
|
||||
/** Type d'acteur Foundry (posé par l'import de structure). */
|
||||
foundryActorType: string | null = null;
|
||||
/** Import de structure Foundry en cours. */
|
||||
importingStructure = false;
|
||||
importStructureError: string | null = null;
|
||||
|
||||
readonly suggestedSections = SUGGESTED_SECTIONS;
|
||||
readonly characterFieldSuggestions = CHARACTER_FIELD_SUGGESTIONS;
|
||||
readonly npcFieldSuggestions = NPC_FIELD_SUGGESTIONS;
|
||||
@@ -112,6 +118,7 @@ export class GameSystemEditComponent implements OnInit {
|
||||
this.characterTemplate = gs.characterTemplate ? [...gs.characterTemplate] : [];
|
||||
this.npcTemplate = gs.npcTemplate ? [...gs.npcTemplate] : [];
|
||||
this.enemyTemplate = gs.enemyTemplate ? [...gs.enemyTemplate] : [];
|
||||
this.foundryActorType = gs.foundryActorType ?? null;
|
||||
},
|
||||
error: () => this.back()
|
||||
});
|
||||
@@ -234,6 +241,47 @@ export class GameSystemEditComponent implements OnInit {
|
||||
return count;
|
||||
}
|
||||
|
||||
// --- Import d'une structure d'acteur Foundry (template ennemi) ------------
|
||||
|
||||
/** Déclenché par le <input file> caché : importe la structure choisie. */
|
||||
onStructureSelected(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
input.value = '';
|
||||
if (file) this.importStructureFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe une structure Foundry (.json du module) : REMPLACE le template ennemi
|
||||
* par les champs mappés + pose le type d'acteur. Nécessite un système déjà
|
||||
* enregistré (id). L'utilisateur élague/renomme ensuite, puis enregistre.
|
||||
*/
|
||||
private importStructureFile(file: File): void {
|
||||
if (!this.id || this.importingStructure) return;
|
||||
this.importStructureError = null;
|
||||
file.text().then(txt => {
|
||||
let structure: unknown;
|
||||
try {
|
||||
structure = JSON.parse(txt);
|
||||
} catch {
|
||||
this.importStructureError = this.translate.instant('gameSystemEdit.structureInvalid');
|
||||
return;
|
||||
}
|
||||
this.importingStructure = true;
|
||||
this.service.importFoundryStructure(this.id!, structure).subscribe({
|
||||
next: (gs) => {
|
||||
this.enemyTemplate = gs.enemyTemplate ? [...gs.enemyTemplate] : [];
|
||||
this.foundryActorType = gs.foundryActorType ?? null;
|
||||
this.importingStructure = false;
|
||||
},
|
||||
error: () => {
|
||||
this.importingStructure = false;
|
||||
this.importStructureError = this.translate.instant('gameSystemEdit.structureFailed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeSection(index: number): void {
|
||||
this.sections.splice(index, 1);
|
||||
}
|
||||
@@ -256,6 +304,7 @@ export class GameSystemEditComponent implements OnInit {
|
||||
characterTemplate: this.characterTemplate,
|
||||
npcTemplate: this.npcTemplate,
|
||||
enemyTemplate: this.enemyTemplate,
|
||||
foundryActorType: this.foundryActorType,
|
||||
isPublic: false
|
||||
};
|
||||
const req = this.id
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface Enemy {
|
||||
order?: number;
|
||||
/** UUID de l'acteur de compendium Foundry d'origine (monstre importé). Null sinon. */
|
||||
foundryRef?: string | null;
|
||||
/** Instantané figé des stats Foundry (aplati clé→valeur), pour affichage en prep. */
|
||||
foundryStats?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface EnemyCreate {
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface GameSystem {
|
||||
characterTemplate?: TemplateField[];
|
||||
npcTemplate?: TemplateField[];
|
||||
enemyTemplate?: TemplateField[];
|
||||
/** Type d'acteur Foundry pour l'export d'ennemis maison typés (import de structure). */
|
||||
foundryActorType?: string | null;
|
||||
author?: string | null;
|
||||
isPublic?: boolean;
|
||||
}
|
||||
@@ -52,6 +54,7 @@ export interface GameSystemCreate {
|
||||
characterTemplate?: TemplateField[];
|
||||
npcTemplate?: TemplateField[];
|
||||
enemyTemplate?: TemplateField[];
|
||||
foundryActorType?: string | null;
|
||||
author?: string | null;
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ export class GameSystemService {
|
||||
return this.http.get<GameSystem[]>(`${this.apiUrl}/search`, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe une structure d'acteur Foundry (exportée par le module) : remplace le
|
||||
* template ennemi par les champs mappés + pose le type d'acteur. Renvoie le système.
|
||||
*/
|
||||
importFoundryStructure(id: string, structure: unknown): Observable<GameSystem> {
|
||||
return this.http.post<GameSystem>(`${this.apiUrl}/${id}/import-foundry-structure`, structure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe un PDF de règles : renvoie une PROPOSITION de sections (titre →
|
||||
* markdown). Rien n'est persisté côté serveur — l'appelant injecte les
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface TemplateField {
|
||||
* KEY_VALUE_LIST = libelles des lignes ; TABLE = noms des colonnes.
|
||||
*/
|
||||
labels?: string[] | null;
|
||||
/** Chemin Foundry du champ (system.<path>) quand le template est calqué Foundry. */
|
||||
foundryPath?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
<div class="picker">
|
||||
|
||||
<!-- Chips des ennemis déjà liés -->
|
||||
@if (linkedEnemies.length > 0) {
|
||||
<!-- Chips des ennemis déjà liés (avec quantité) -->
|
||||
@if (linkedGroups.length > 0) {
|
||||
<div class="linked-chips">
|
||||
@for (e of linkedEnemies; track e.id) {
|
||||
@for (g of linkedGroups; track g.enemy.id) {
|
||||
<span class="chip">
|
||||
<a
|
||||
class="chip-label"
|
||||
[href]="enemyUrl(e.id!)"
|
||||
[href]="enemyUrl(g.enemy.id!)"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
[title]="'enemyLinkPicker.openInNewTab' | translate">
|
||||
<lucide-icon [img]="Skull" [size]="12"></lucide-icon>
|
||||
{{ label(e) }}
|
||||
{{ label(g.enemy) }}
|
||||
</a>
|
||||
<button type="button" class="chip-remove" (click)="remove(e.id!)" [title]="'enemyLinkPicker.removeLink' | translate">
|
||||
<span class="chip-qty">
|
||||
<button type="button" class="chip-step" (click)="decrement(g.enemy.id!)" [title]="'enemyLinkPicker.decrement' | translate">−</button>
|
||||
<span class="chip-count">×{{ g.count }}</span>
|
||||
<button type="button" class="chip-step" (click)="increment(g.enemy.id!)" [title]="'enemyLinkPicker.increment' | translate">+</button>
|
||||
</span>
|
||||
<button type="button" class="chip-remove" (click)="remove(g.enemy.id!)" [title]="'enemyLinkPicker.removeLink' | translate">
|
||||
<lucide-icon [img]="X" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -33,6 +33,32 @@
|
||||
&:hover { color: #fecdd3; }
|
||||
}
|
||||
|
||||
.chip-qty {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-left: 1px solid #2a2a3d;
|
||||
|
||||
.chip-step {
|
||||
width: 1.4rem;
|
||||
padding: 0.25rem 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
&:hover { opacity: 1; background: #2a2a3d; color: #fecdd3; }
|
||||
}
|
||||
|
||||
.chip-count {
|
||||
min-width: 1.6rem;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.chip-remove {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -44,11 +44,20 @@ export class EnemyLinkPickerComponent {
|
||||
/** true tant que l'input a le focus (pour afficher le dropdown). */
|
||||
dropdownOpen = false;
|
||||
|
||||
/** Ennemis actuellement liés (résolus en objets complets pour affichage). */
|
||||
get linkedEnemies(): Enemy[] {
|
||||
return this.value
|
||||
.map(id => this.availableEnemies.find(e => e.id === id))
|
||||
.filter((e): e is Enemy => !!e);
|
||||
/**
|
||||
* Ennemis liés groupés AVEC leur quantité (le même id peut apparaître N fois
|
||||
* dans `value` → N tokens à l'export). Ordre = première apparition.
|
||||
*/
|
||||
get linkedGroups(): { enemy: Enemy; count: number }[] {
|
||||
const counts = new Map<string, number>();
|
||||
const order: string[] = [];
|
||||
for (const id of this.value) {
|
||||
if (!counts.has(id)) order.push(id);
|
||||
counts.set(id, (counts.get(id) ?? 0) + 1);
|
||||
}
|
||||
return order
|
||||
.map(id => ({ enemy: this.availableEnemies.find(e => e.id === id), count: counts.get(id)! }))
|
||||
.filter((g): g is { enemy: Enemy; count: number } => !!g.enemy);
|
||||
}
|
||||
|
||||
/** Ennemis proposables dans le dropdown — filtrés par query, exclut les déjà liés. */
|
||||
@@ -66,14 +75,28 @@ export class EnemyLinkPickerComponent {
|
||||
return level ? `${enemy.name} (${level})` : enemy.name;
|
||||
}
|
||||
|
||||
/** Ajoute un ennemi aux liens. */
|
||||
/** Ajoute un ennemi aux liens (première occurrence). */
|
||||
add(enemy: Enemy): void {
|
||||
if (!enemy.id || this.value.includes(enemy.id)) return;
|
||||
if (!enemy.id) return;
|
||||
this.valueChange.emit([...this.value, enemy.id]);
|
||||
this.query = '';
|
||||
}
|
||||
|
||||
/** Retire un ennemi des liens. */
|
||||
/** +1 du même ennemi (un token de plus à l'export). */
|
||||
increment(enemyId: string): void {
|
||||
this.valueChange.emit([...this.value, enemyId]);
|
||||
}
|
||||
|
||||
/** −1 du même ennemi (retire UNE occurrence). */
|
||||
decrement(enemyId: string): void {
|
||||
const i = this.value.indexOf(enemyId);
|
||||
if (i < 0) return;
|
||||
const next = [...this.value];
|
||||
next.splice(i, 1);
|
||||
this.valueChange.emit(next);
|
||||
}
|
||||
|
||||
/** Retire TOUTES les occurrences de cet ennemi. */
|
||||
remove(enemyId: string): void {
|
||||
this.valueChange.emit(this.value.filter(id => id !== enemyId));
|
||||
}
|
||||
|
||||
@@ -368,6 +368,8 @@
|
||||
"searchPlaceholder": "Search for an enemy from the bestiary...",
|
||||
"openInNewTab": "Open the sheet in a new tab",
|
||||
"removeLink": "Remove link",
|
||||
"increment": "One more",
|
||||
"decrement": "One less",
|
||||
"noMatch": "No matching enemy"
|
||||
},
|
||||
"personaView": {
|
||||
@@ -1096,7 +1098,9 @@
|
||||
"deleteMessage": "Delete \"{{name}}\"?"
|
||||
},
|
||||
"enemyView": {
|
||||
"levelLong": "Level {{level}}"
|
||||
"levelLong": "Level {{level}}",
|
||||
"foundryStatsTitle": "Foundry stats",
|
||||
"foundryStatsHint": "Snapshot imported from Foundry (frozen — the live sheet stays in Foundry)."
|
||||
},
|
||||
"gameSystems": {
|
||||
"heroTitle": "RPG Systems",
|
||||
@@ -1141,6 +1145,11 @@
|
||||
"npcFieldsHint": "Shown when creating/editing a non-player character.",
|
||||
"enemyFieldsLabel": "Enemy sheet fields",
|
||||
"enemyFieldsHint": "Shown when creating/editing an enemy (bestiary). Name, level, folder, portrait and banner are automatic.",
|
||||
"structureImport": "Import a Foundry structure",
|
||||
"structureImporting": "Importing…",
|
||||
"structureActorType": "Foundry actor: {{type}}",
|
||||
"structureInvalid": "Unreadable structure (invalid JSON).",
|
||||
"structureFailed": "Structure import failed.",
|
||||
"importExtracting": "Extracting text…",
|
||||
"importAnalyzing": "Analyzing rules… ({{current}}/{{total}})",
|
||||
"importFailed": "PDF import failed.",
|
||||
|
||||
@@ -368,6 +368,8 @@
|
||||
"searchPlaceholder": "Rechercher un ennemi du bestiaire...",
|
||||
"openInNewTab": "Ouvrir la fiche dans un nouvel onglet",
|
||||
"removeLink": "Retirer le lien",
|
||||
"increment": "Un de plus",
|
||||
"decrement": "Un de moins",
|
||||
"noMatch": "Aucun ennemi ne correspond"
|
||||
},
|
||||
"personaView": {
|
||||
@@ -1096,7 +1098,9 @@
|
||||
"deleteMessage": "Supprimer « {{name}} » ?"
|
||||
},
|
||||
"enemyView": {
|
||||
"levelLong": "Niveau {{level}}"
|
||||
"levelLong": "Niveau {{level}}",
|
||||
"foundryStatsTitle": "Statistiques Foundry",
|
||||
"foundryStatsHint": "Instantané importé depuis Foundry (figé — la fiche vivante reste côté Foundry)."
|
||||
},
|
||||
"gameSystems": {
|
||||
"heroTitle": "Systèmes de JDR",
|
||||
@@ -1141,6 +1145,11 @@
|
||||
"npcFieldsHint": "Affichés lors de la création/édition d'un personnage non-joueur.",
|
||||
"enemyFieldsLabel": "Champs de la fiche Ennemi",
|
||||
"enemyFieldsHint": "Affichés lors de la création/édition d'un ennemi (bestiaire). Nom, niveau, dossier, portrait et bandeau sont automatiques.",
|
||||
"structureImport": "Importer une structure Foundry",
|
||||
"structureImporting": "Import…",
|
||||
"structureActorType": "Acteur Foundry : {{type}}",
|
||||
"structureInvalid": "Structure illisible (JSON invalide).",
|
||||
"structureFailed": "Échec de l'import de la structure.",
|
||||
"importExtracting": "Extraction du texte…",
|
||||
"importAnalyzing": "Analyse des règles… ({{current}}/{{total}})",
|
||||
"importFailed": "Échec de l'import du PDF.",
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
"deleteMessage": "Delete \"{{name}}\"?"
|
||||
},
|
||||
"enemyView": {
|
||||
"levelLong": "Level {{level}}"
|
||||
"levelLong": "Level {{level}}",
|
||||
"foundryStatsTitle": "Foundry stats",
|
||||
"foundryStatsHint": "Snapshot imported from Foundry (frozen — the live sheet stays in Foundry)."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
"deleteMessage": "Supprimer « {{name}} » ?"
|
||||
},
|
||||
"enemyView": {
|
||||
"levelLong": "Niveau {{level}}"
|
||||
"levelLong": "Niveau {{level}}",
|
||||
"foundryStatsTitle": "Statistiques Foundry",
|
||||
"foundryStatsHint": "Instantané importé depuis Foundry (figé — la fiche vivante reste côté Foundry)."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
"npcFieldsHint": "Shown when creating/editing a non-player character.",
|
||||
"enemyFieldsLabel": "Enemy sheet fields",
|
||||
"enemyFieldsHint": "Shown when creating/editing an enemy (bestiary). Name, level, folder, portrait and banner are automatic.",
|
||||
"structureImport": "Import a Foundry structure",
|
||||
"structureImporting": "Importing…",
|
||||
"structureActorType": "Foundry actor: {{type}}",
|
||||
"structureInvalid": "Unreadable structure (invalid JSON).",
|
||||
"structureFailed": "Structure import failed.",
|
||||
"importExtracting": "Extracting text…",
|
||||
"importAnalyzing": "Analyzing rules… ({{current}}/{{total}})",
|
||||
"importFailed": "PDF import failed.",
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
"npcFieldsHint": "Affichés lors de la création/édition d'un personnage non-joueur.",
|
||||
"enemyFieldsLabel": "Champs de la fiche Ennemi",
|
||||
"enemyFieldsHint": "Affichés lors de la création/édition d'un ennemi (bestiaire). Nom, niveau, dossier, portrait et bandeau sont automatiques.",
|
||||
"structureImport": "Importer une structure Foundry",
|
||||
"structureImporting": "Import…",
|
||||
"structureActorType": "Acteur Foundry : {{type}}",
|
||||
"structureInvalid": "Structure illisible (JSON invalide).",
|
||||
"structureFailed": "Échec de l'import de la structure.",
|
||||
"importExtracting": "Extraction du texte…",
|
||||
"importAnalyzing": "Analyse des règles… ({{current}}/{{total}})",
|
||||
"importFailed": "Échec de l'import du PDF.",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"searchPlaceholder": "Search for an enemy from the bestiary...",
|
||||
"openInNewTab": "Open the sheet in a new tab",
|
||||
"removeLink": "Remove link",
|
||||
"increment": "One more",
|
||||
"decrement": "One less",
|
||||
"noMatch": "No matching enemy"
|
||||
},
|
||||
"personaView": {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"searchPlaceholder": "Rechercher un ennemi du bestiaire...",
|
||||
"openInNewTab": "Ouvrir la fiche dans un nouvel onglet",
|
||||
"removeLink": "Retirer le lien",
|
||||
"increment": "Un de plus",
|
||||
"decrement": "Un de moins",
|
||||
"noMatch": "Aucun ennemi ne correspond"
|
||||
},
|
||||
"personaView": {
|
||||
|
||||
Reference in New Issue
Block a user