Mise en place de l'anglais comme deuxième langue pour l'application
Some checks failed
Build & Push Images / build (brain) (push) Has been cancelled
Build & Push Images / build (core) (push) Has been cancelled
Build & Push Images / build (web) (push) Has been cancelled
Build & Push Images / build-switcher (push) Has been cancelled

This commit is contained in:
2026-06-14 16:24:05 +02:00
parent 6e75326779
commit af3a6d443c
198 changed files with 7801 additions and 1720 deletions

View File

@@ -5,9 +5,9 @@
@if (messages.length === 0 && !currentAssistantText && !error) {
<div class="welcome-hint">
<lucide-icon [img]="Sparkles" [size]="18"></lucide-icon>
<p>Pose une question à l'IA pendant la partie.</p>
<p>{{ 'sessionAiChatPanel.welcome' | translate }}</p>
<p class="welcome-sub">
Elle connaît ton univers, ta campagne, les règles du système et tout ce qui a été noté dans le journal.
{{ 'sessionAiChatPanel.welcomeSub' | translate }}
</p>
</div>
}
@@ -20,10 +20,10 @@
type="button"
class="msg-action"
[disabled]="!canSaveToJournal"
[title]="canSaveToJournal ? 'Ajouter cette réponse au journal' : 'Session terminée'"
[title]="(canSaveToJournal ? 'sessionAiChatPanel.saveReplyTitle' : 'sessionAiChatPanel.sessionEnded') | translate"
(click)="onSaveToJournal(m.content)">
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon>
Au journal
{{ 'sessionAiChatPanel.toJournal' | translate }}
</button>
}
</div>
@@ -47,7 +47,7 @@
[(ngModel)]="input"
name="aiChatInput"
rows="2"
[placeholder]="isStreaming ? 'LIA répond' : 'Demande une idée, un rebondissement, une description'"
[placeholder]="(isStreaming ? 'sessionAiChatPanel.replying' : 'sessionAiChatPanel.inputPlaceholder') | translate"
[disabled]="isStreaming"
(keydown.control.enter)="send()"></textarea>
@@ -56,7 +56,7 @@
class="btn-link"
[disabled]="messages.length === 0 && !currentAssistantText"
(click)="clearConversation()"
title="Effacer la conversation">
[title]="'sessionAiChatPanel.clearConversation' | translate">
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
</button>
@@ -67,7 +67,7 @@
[disabled]="!input.trim()"
(click)="send()">
<lucide-icon [img]="Send" [size]="14"></lucide-icon>
Envoyer
{{ 'sessionAiChatPanel.send' | translate }}
</button>
}
@@ -77,7 +77,7 @@
class="btn-secondary btn-send"
(click)="cancelStream()">
<lucide-icon [img]="Square" [size]="14"></lucide-icon>
Stop
{{ 'sessionAiChatPanel.stop' | translate }}
</button>
}
</div>

View File

@@ -4,6 +4,7 @@ import {
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import {
LucideAngularModule, Send, Sparkles, Trash2, BookmarkPlus, Square
} from 'lucide-angular';
@@ -23,7 +24,7 @@ import { AiChatService, ChatMessage } from '../../services/ai-chat.service';
*/
@Component({
selector: 'app-session-ai-chat-panel',
imports: [FormsModule, LucideAngularModule],
imports: [FormsModule, LucideAngularModule, TranslatePipe],
templateUrl: './session-ai-chat-panel.component.html',
styleUrls: ['./session-ai-chat-panel.component.scss']
})
@@ -50,7 +51,7 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
private streamSub: Subscription | null = null;
constructor(private aiChat: AiChatService) {}
constructor(private aiChat: AiChatService, private translate: TranslateService) {}
ngOnChanges(changes: SimpleChanges): void {
// Reset complet si on change de session (changement d'instance jouée).
@@ -87,8 +88,8 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
}
},
error: (err: unknown) => {
const message = err instanceof Error ? err.message : 'Erreur inconnue';
this.error = `Erreur IA : ${message}`;
const message = err instanceof Error ? err.message : this.translate.instant('sessionAiChatPanel.unknownError');
this.error = this.translate.instant('sessionAiChatPanel.aiError', { message });
this.isStreaming = false;
this.streamSub = null;
},
@@ -115,7 +116,7 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
}
// On garde ce qui a déjà été streamé : utile si l'IA partait dans le mur.
if (this.currentAssistantText.trim()) {
this.messages = [...this.messages, { role: 'assistant', content: this.currentAssistantText + ' [interrompu]' }];
this.messages = [...this.messages, { role: 'assistant', content: this.currentAssistantText + ' ' + this.translate.instant('sessionAiChatPanel.interrupted') }];
}
this.currentAssistantText = '';
this.isStreaming = false;

View File

@@ -2,7 +2,7 @@
<div class="session-detail">
<a class="back-link" [routerLink]="campaignId ? ['/campaigns', campaignId] : ['/campaigns']">
<lucide-icon [img]="ArrowLeft" [size]="14"></lucide-icon>
Retour à la campagne
{{ 'sessionDetail.backToCampaign' | translate }}
</a>
<div class="detail-header">
<div class="header-texts">
@@ -12,7 +12,7 @@
<lucide-icon [img]="Dices" [size]="24"></lucide-icon>
{{ session.name }}
</h1>
<button type="button" class="btn-icon" (click)="startRename()" title="Renommer la session">
<button type="button" class="btn-icon" (click)="startRename()" [title]="'sessionDetail.renameTitle' | translate">
<lucide-icon [img]="Pencil" [size]="14"></lucide-icon>
</button>
</div>
@@ -25,22 +25,22 @@
(keydown.enter)="saveRename()"
(keydown.escape)="cancelRename()"
autofocus />
<button type="button" class="btn-icon" (click)="saveRename()" [disabled]="!editName.trim()" title="Valider">
<button type="button" class="btn-icon" (click)="saveRename()" [disabled]="!editName.trim()" [title]="'common.validate' | translate">
<lucide-icon [img]="Check" [size]="14"></lucide-icon>
</button>
<button type="button" class="btn-icon" (click)="cancelRename()" title="Annuler">
Annuler
<button type="button" class="btn-icon" (click)="cancelRename()" [title]="'common.cancel' | translate">
{{ 'common.cancel' | translate }}
</button>
</div>
}
<div class="meta">
<span class="badge" [class.badge-active]="session.active">
{{ session.active ? 'En cours' : 'Terminée' }}
{{ (session.active ? 'sessionDetail.statusActive' : 'sessionDetail.statusEnded') | translate }}
</span>
<span class="badge badge-muted">Démarrée le {{ session.startedAt | date:'dd/MM/yyyy HH:mm' }}</span>
<span class="badge badge-muted">{{ 'sessionDetail.startedOn' | translate:{ date: (session.startedAt | date:'dd/MM/yyyy HH:mm') } }}</span>
@if (session.endedAt) {
<span class="badge badge-muted">
Terminée le {{ session.endedAt | date:'dd/MM/yyyy HH:mm' }}
{{ 'sessionDetail.endedOn' | translate:{ date: (session.endedAt | date:'dd/MM/yyyy HH:mm') } }}
</span>
}
</div>
@@ -49,12 +49,12 @@
@if (session.active) {
<button type="button" class="btn-secondary" (click)="endSession()">
<lucide-icon [img]="Square" [size]="14"></lucide-icon>
Terminer la session
{{ 'sessionDetail.endSession' | translate }}
</button>
}
<button type="button" class="btn-danger" (click)="deleteSession()">
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
Supprimer
{{ 'common.delete' | translate }}
</button>
</div>
</div>
@@ -74,7 +74,7 @@
[style.--type-color]="entryTypeMeta[type].color"
(click)="newEntryType = type">
<lucide-icon [img]="typeIcons[type]" [size]="14"></lucide-icon>
{{ entryTypeMeta[type].label }}
{{ typeLabel(type) }}
</button>
}
</div>
@@ -82,29 +82,29 @@
[(ngModel)]="newEntryContent"
name="newEntryContent"
rows="3"
[placeholder]="'Ajouter une ' + entryTypeMeta[newEntryType].label.toLowerCase() + ''"
[placeholder]="newEntryPlaceholder()"
(keydown.control.enter)="submitNewEntry()"></textarea>
<div class="entry-input-footer">
<span class="hint">Ctrl + Entrée pour ajouter</span>
<span class="hint">{{ 'sessionDetail.ctrlEnterHint' | translate }}</span>
<button type="button"
class="btn-primary"
[disabled]="!newEntryContent.trim() || submittingEntry"
(click)="submitNewEntry()">
<lucide-icon [img]="Plus" [size]="14"></lucide-icon>
Ajouter
{{ 'common.add' | translate }}
</button>
</div>
</section>
}
<!-- Timeline -->
<section class="detail-section timeline-section">
<h2>Journal de session</h2>
<h2>{{ 'sessionDetail.journalTitle' | translate }}</h2>
@if (entries.length === 0) {
<div class="empty-state">
<p>Aucune entrée pour le moment.</p>
<p>{{ 'sessionDetail.noEntries' | translate }}</p>
@if (session.active) {
<p class="hint">
Saisis une note, un évènement ou un jet ci-dessus pour commencer le journal.
{{ 'sessionDetail.noEntriesHint' | translate }}
</p>
}
</div>
@@ -121,13 +121,13 @@
<!-- Mode lecture -->
@if (editingEntryId !== entry.id) {
<div class="entry-header">
<span class="entry-type">{{ entryTypeMeta[entry.type].label }}</span>
<span class="entry-type">{{ typeLabel(entry.type) }}</span>
<span class="entry-time">{{ entry.occurredAt | date:'HH:mm — dd/MM/yyyy' }}</span>
<div class="entry-actions">
<button type="button" class="btn-icon" (click)="startEditEntry(entry)" title="Modifier">
<button type="button" class="btn-icon" (click)="startEditEntry(entry)" [title]="'common.edit' | translate">
<lucide-icon [img]="Pencil" [size]="12"></lucide-icon>
</button>
<button type="button" class="btn-icon btn-icon--danger" (click)="deleteEntry(entry)" title="Supprimer">
<button type="button" class="btn-icon btn-icon--danger" (click)="deleteEntry(entry)" [title]="'common.delete' | translate">
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
</button>
</div>
@@ -145,7 +145,7 @@
[style.--type-color]="entryTypeMeta[type].color"
(click)="editEntryType = type">
<lucide-icon [img]="typeIcons[type]" [size]="12"></lucide-icon>
{{ entryTypeMeta[type].label }}
{{ typeLabel(type) }}
</button>
}
</div>
@@ -158,14 +158,14 @@
<div class="entry-input-footer">
<button type="button" class="btn-secondary btn-sm" (click)="cancelEditEntry()">
<lucide-icon [img]="X" [size]="12"></lucide-icon>
Annuler
{{ 'common.cancel' | translate }}
</button>
<button type="button"
class="btn-primary btn-sm"
[disabled]="!editEntryContent.trim()"
(click)="saveEditEntry(entry)">
<lucide-icon [img]="Check" [size]="12"></lucide-icon>
Sauvegarder
{{ 'common.save' | translate }}
</button>
</div>
}

View File

@@ -2,6 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import {
LucideAngularModule, LucideIconData,
Dices, ArrowLeft, Square, Trash2, Pencil, Check,
@@ -29,7 +30,7 @@ import { DiceRollResult } from '../session-dice-panel/session-dice-panel.compone
*/
@Component({
selector: 'app-session-detail',
imports: [CommonModule, FormsModule, LucideAngularModule, RouterLink, SessionReferencePanelComponent],
imports: [CommonModule, FormsModule, LucideAngularModule, TranslatePipe, RouterLink, SessionReferencePanelComponent],
templateUrl: './session-detail.component.html',
styleUrls: ['./session-detail.component.scss']
})
@@ -80,9 +81,22 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
private entryService: SessionEntryService,
private layoutService: LayoutService,
private pageTitleService: PageTitleService,
private confirmDialog: ConfirmDialogService
private confirmDialog: ConfirmDialogService,
private translate: TranslateService
) {}
/** Libellé traduit d'un type d'entrée (le modèle partagé reste en FR pour la donnée). */
typeLabel(type: EntryType): string {
return this.translate.instant('sessionDetail.entryType.' + type);
}
/** Placeholder de la zone de saisie, dépendant du type sélectionné. */
newEntryPlaceholder(): string {
return this.translate.instant('sessionDetail.addEntryPlaceholder', {
type: this.typeLabel(this.newEntryType).toLowerCase()
});
}
ngOnInit(): void {
this.layoutService.hide();
this.route.paramMap.pipe(
@@ -144,10 +158,10 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
if (!this.session || !this.session.active) return;
const session = this.session;
this.confirmDialog.confirm({
title: 'Terminer la session ?',
message: `Marquer la session "${session.name}" comme terminée ?`,
details: ['Tu pourras toujours consulter son contenu après.'],
confirmLabel: 'Terminer',
title: this.translate.instant('sessionDetail.endConfirm.title'),
message: this.translate.instant('sessionDetail.endConfirm.message', { name: session.name }),
details: [this.translate.instant('sessionDetail.endConfirm.detail')],
confirmLabel: this.translate.instant('sessionDetail.endConfirm.confirmLabel'),
variant: 'warning'
}).then(ok => {
if (!ok) return;
@@ -162,17 +176,20 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
if (!this.session) return;
const session = this.session;
const entryCount = this.entries.length;
const entriesDetail = entryCount === 0
? this.translate.instant('sessionDetail.deleteConfirm.noEntries')
: entryCount === 1
? this.translate.instant('sessionDetail.deleteConfirm.entriesOne')
: this.translate.instant('sessionDetail.deleteConfirm.entriesMany', { n: entryCount });
const details = [
entryCount > 0
? `${entryCount} entrée${entryCount > 1 ? 's' : ''} de journal sera également supprimée.`
: 'Aucune entrée de journal pour cette session.',
'Cette action est irréversible.'
entriesDetail,
this.translate.instant('sessionDetail.deleteConfirm.irreversible')
];
this.confirmDialog.confirm({
title: 'Supprimer la session ?',
message: `Supprimer définitivement la session "${session.name}" ?`,
title: this.translate.instant('sessionDetail.deleteConfirm.title'),
message: this.translate.instant('sessionDetail.deleteConfirm.message', { name: session.name }),
details,
confirmLabel: 'Supprimer',
confirmLabel: this.translate.instant('common.delete'),
variant: 'danger'
}).then(ok => {
if (!ok) return;
@@ -284,9 +301,9 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
if (!this.session) return;
const session = this.session;
this.confirmDialog.confirm({
title: 'Supprimer cette entrée ?',
message: 'Cette entrée du journal sera définitivement supprimée.',
confirmLabel: 'Supprimer',
title: this.translate.instant('sessionDetail.deleteEntryConfirm.title'),
message: this.translate.instant('sessionDetail.deleteEntryConfirm.message'),
confirmLabel: this.translate.instant('common.delete'),
variant: 'danger'
}).then(ok => {
if (!ok) return;

View File

@@ -15,26 +15,26 @@
<div class="dice-inputs">
<label class="input-group">
<span>Nombre</span>
<span>{{ 'sessionDicePanel.count' | translate }}</span>
<input type="number" min="1" max="20" [(ngModel)]="count" />
</label>
<label class="input-group">
<span>Modificateur</span>
<span>{{ 'sessionDicePanel.modifier' | translate }}</span>
<input type="number" [(ngModel)]="modifier" />
</label>
</div>
<button type="button" class="btn-primary btn-roll" (click)="roll()">
<lucide-icon [img]="Dices" [size]="16"></lucide-icon>
Lancer {{ count }}d{{ selectedFace }}{{ modifier === 0 ? '' : (modifier > 0 ? '+' + modifier : modifier) }}
{{ 'sessionDicePanel.roll' | translate }} {{ count }}d{{ selectedFace }}{{ modifier === 0 ? '' : (modifier > 0 ? '+' + modifier : modifier) }}
</button>
</div>
@if (history.length > 0) {
<div class="dice-history">
<div class="history-header">
<span>Derniers jets</span>
<button type="button" class="btn-link" (click)="clearHistory()" title="Vider l'historique local">
<span>{{ 'sessionDicePanel.lastRolls' | translate }}</span>
<button type="button" class="btn-link" (click)="clearHistory()" [title]="'sessionDicePanel.clearHistory' | translate">
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
</button>
</div>
@@ -51,7 +51,7 @@
<button type="button"
class="btn-icon"
[disabled]="!canAddToJournal"
[title]="canAddToJournal ? 'Ajouter au journal' : 'Session terminée'"
[title]="(canAddToJournal ? 'sessionDicePanel.addToJournal' : 'sessionDicePanel.sessionEnded') | translate"
(click)="addToJournal(r)">
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon>
</button>
@@ -63,7 +63,7 @@
@if (history.length === 0) {
<p class="placeholder-hint">
Choisis un dé et lance.
{{ 'sessionDicePanel.placeholder' | translate }}
</p>
}

View File

@@ -1,6 +1,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslatePipe } from '@ngx-translate/core';
import { LucideAngularModule, Dices, BookmarkPlus, Trash2 } from 'lucide-angular';
/** Faces de dés supportées par le roller. */
@@ -26,7 +27,7 @@ export interface DiceRollResult {
*/
@Component({
selector: 'app-session-dice-panel',
imports: [FormsModule, LucideAngularModule],
imports: [FormsModule, LucideAngularModule, TranslatePipe],
templateUrl: './session-dice-panel.component.html',
styleUrls: ['./session-dice-panel.component.scss']
})

View File

@@ -1,6 +1,6 @@
<div class="sic-panel">
@if (loading) {
<p class="loading-hint">Chargement…</p>
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
}
<!-- Liste des catalogues -->
@@ -8,7 +8,7 @@
<div class="sic-list">
@if (catalogs.length === 0) {
<p class="empty-hint">
Aucun catalogue d'objets dans cette campagne.
{{ 'sessionItemCatalogsPanel.empty' | translate }}
</p>
}
@for (c of catalogs; track c) {
@@ -25,7 +25,7 @@
@if (selected) {
<div class="sic-detail">
<button type="button" class="sic-back" (click)="backToList()">
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> Catalogues
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> {{ 'sessionItemCatalogsPanel.catalogs' | translate }}
</button>
<h4>{{ selected.name }}</h4>
@for (g of groups; track g) {
@@ -45,8 +45,8 @@
<div class="sic-obj-desc">{{ it.description }}</div>
}
<button type="button" class="sic-note" (click)="note(it)" [disabled]="!canAddToJournal"
title="Consigner au journal (ex. achat)">
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon> Journal
[title]="'sessionItemCatalogsPanel.noteTitle' | translate">
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon> {{ 'sessionItemCatalogsPanel.journal' | translate }}
</button>
</div>
}

View File

@@ -1,5 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { LucideAngularModule, Package, BookmarkPlus, ChevronLeft } from 'lucide-angular';
import { catchError, of } from 'rxjs';
import { ItemCatalogService } from '../../services/item-catalog.service';
@@ -13,7 +14,7 @@ interface ItemGroup { category: string; items: CatalogItem[]; }
*/
@Component({
selector: 'app-session-item-catalogs-panel',
imports: [LucideAngularModule],
imports: [LucideAngularModule, TranslatePipe],
templateUrl: './session-item-catalogs-panel.component.html',
styleUrls: ['./session-item-catalogs-panel.component.scss']
})

View File

@@ -1,6 +1,6 @@
<div class="srt-panel">
@if (loading) {
<p class="loading-hint">Chargement…</p>
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
}
<!-- Liste des tables -->
@@ -8,7 +8,7 @@
<div class="srt-list">
@if (tables.length === 0) {
<p class="empty-hint">
Aucune table aléatoire dans cette campagne. Crée-en une depuis la sidebar.
{{ 'sessionRandomTablesPanel.empty' | translate }}
</p>
}
@for (t of tables; track t) {
@@ -25,12 +25,12 @@
@if (selected) {
<div class="srt-detail">
<button type="button" class="srt-back" (click)="backToList()">
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> Tables
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> {{ 'sessionRandomTablesPanel.tables' | translate }}
</button>
<h4>{{ selected.name }}</h4>
<button type="button" class="srt-roll" (click)="roll()">
<lucide-icon [img]="Dices" [size]="15"></lucide-icon>
Lancer {{ selected.diceFormula }}
{{ 'sessionRandomTablesPanel.roll' | translate }} {{ selected.diceFormula }}
</button>
@if (lastRoll) {
<div class="srt-result">
@@ -44,7 +44,7 @@
<strong>{{ matched.label }}</strong>
}
@if (!matched) {
<em>aucune entrée</em>
<em>{{ 'sessionRandomTablesPanel.noEntry' | translate }}</em>
}
</div>
@if (matched?.detail) {
@@ -52,12 +52,12 @@
}
<div class="srt-actions">
<button type="button" class="srt-act" (click)="addToJournal()" [disabled]="!canAddToJournal">
<lucide-icon [img]="BookmarkPlus" [size]="13"></lucide-icon> Journal
<lucide-icon [img]="BookmarkPlus" [size]="13"></lucide-icon> {{ 'sessionRandomTablesPanel.journal' | translate }}
</button>
<button type="button" class="srt-act srt-act--ai" (click)="improvise()"
[disabled]="!matched || improvising || !canAddToJournal">
<lucide-icon [img]="Sparkles" [size]="13"></lucide-icon>
{{ improvising ? '…' : 'IA improvise' }}
{{ improvising ? ('sessionRandomTablesPanel.improvising' | translate) : ('sessionRandomTablesPanel.aiImprovise' | translate) }}
</button>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { LucideAngularModule, Dices, BookmarkPlus, Sparkles, ChevronLeft } from 'lucide-angular';
import { catchError, of } from 'rxjs';
import { RandomTableService } from '../../services/random-table.service';
@@ -18,7 +19,7 @@ import { DiceRollResult } from '../session-dice-panel/session-dice-panel.compone
*/
@Component({
selector: 'app-session-random-tables-panel',
imports: [LucideAngularModule],
imports: [LucideAngularModule, TranslatePipe],
templateUrl: './session-random-tables-panel.component.html',
styleUrls: ['./session-random-tables-panel.component.scss']
})
@@ -41,7 +42,7 @@ export class SessionRandomTablesPanelComponent implements OnInit {
matched: RandomTableEntry | null = null;
improvising = false;
constructor(private service: RandomTableService) {}
constructor(private service: RandomTableService, private translate: TranslateService) {}
ngOnInit(): void {
if (!this.campaignId) return;
@@ -79,7 +80,7 @@ export class SessionRandomTablesPanelComponent implements OnInit {
/** Consigne le tirage au journal (entrée DICE_ROLL via la sortie `rolled`). */
addToJournal(): void {
if (!this.canAddToJournal || !this.selected || !this.lastRoll) return;
const label = this.matched?.label ?? 'aucun résultat';
const label = this.matched?.label ?? this.translate.instant('sessionRandomTablesPanel.noResult');
const result: DiceRollResult = {
notation: this.selected.diceFormula,
rolls: this.lastRoll.rolls,

View File

@@ -6,42 +6,42 @@
[class.ref-tab--active]="activeTab === 'ai'"
(click)="selectTab('ai')">
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
IA
{{ 'sessionReferencePanel.tabAi' | translate }}
</button>
<button type="button"
class="ref-tab"
[class.ref-tab--active]="activeTab === 'dice'"
(click)="selectTab('dice')">
<lucide-icon [img]="Dices" [size]="14"></lucide-icon>
Dés
{{ 'sessionReferencePanel.tabDice' | translate }}
</button>
<button type="button"
class="ref-tab"
[class.ref-tab--active]="activeTab === 'tables'"
(click)="selectTab('tables')">
<lucide-icon [img]="Table2" [size]="14"></lucide-icon>
Tables
{{ 'sessionReferencePanel.tabTables' | translate }}
</button>
<button type="button"
class="ref-tab"
[class.ref-tab--active]="activeTab === 'objects'"
(click)="selectTab('objects')">
<lucide-icon [img]="Package" [size]="14"></lucide-icon>
Objets
{{ 'sessionReferencePanel.tabObjects' | translate }}
</button>
<button type="button"
class="ref-tab"
[class.ref-tab--active]="activeTab === 'characters'"
(click)="selectTab('characters')">
<lucide-icon [img]="User" [size]="14"></lucide-icon>
PJ/PNJ
{{ 'sessionReferencePanel.tabCharacters' | translate }}
</button>
<button type="button"
class="ref-tab"
[class.ref-tab--active]="activeTab === 'scenes'"
(click)="selectTab('scenes')">
<lucide-icon [img]="Swords" [size]="14"></lucide-icon>
Scènes
{{ 'sessionReferencePanel.tabScenes' | translate }}
</button>
</nav>
@@ -87,7 +87,7 @@
@if (activeTab === 'characters') {
<div class="ref-list">
@if (loadingChars) {
<p class="loading-hint">Chargement…</p>
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
}
@if (!loadingChars) {
<div>
@@ -95,7 +95,7 @@
<div class="ref-group">
<h4>
<lucide-icon [img]="User" [size]="13"></lucide-icon>
Personnages joueurs
{{ 'sessionReferencePanel.playerCharacters' | translate }}
</h4>
@for (c of characters; track c) {
<button
@@ -112,7 +112,7 @@
<div class="ref-group">
<h4>
<lucide-icon [img]="Drama" [size]="13"></lucide-icon>
Personnages non-joueurs
{{ 'sessionReferencePanel.nonPlayerCharacters' | translate }}
</h4>
@for (n of npcs; track n) {
<button
@@ -127,7 +127,7 @@
}
@if (characters.length === 0 && npcs.length === 0) {
<p class="empty-hint">
Aucun personnage dans cette campagne.
{{ 'sessionReferencePanel.emptyCharacters' | translate }}
</p>
}
</div>
@@ -139,12 +139,12 @@
@if (activeTab === 'scenes') {
<div class="ref-list">
@if (loadingTree) {
<p class="loading-hint">Chargement…</p>
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
}
@if (!loadingTree && treeData) {
@if (treeData.arcs.length === 0) {
<p class="empty-hint">
Aucun arc narratif. Construis le scénario de ta campagne pour le retrouver ici.
{{ 'sessionReferencePanel.emptyScenes' | translate }}
</p>
}
@for (arc of treeData.arcs; track arc) {

View File

@@ -1,5 +1,6 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { LucideAngularModule, User, Drama, Swords, Dices, ExternalLink, Sparkles, Table2, Package } from 'lucide-angular';
import { catchError, of } from 'rxjs';
import { CampaignService } from '../../services/campaign.service';
@@ -31,7 +32,7 @@ type TabId = 'dice' | 'tables' | 'objects' | 'characters' | 'scenes' | 'ai';
*/
@Component({
selector: 'app-session-reference-panel',
imports: [LucideAngularModule, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent, SessionItemCatalogsPanelComponent],
imports: [LucideAngularModule, TranslatePipe, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent, SessionItemCatalogsPanelComponent],
templateUrl: './session-reference-panel.component.html',
styleUrls: ['./session-reference-panel.component.scss']
})