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

@@ -1,58 +1,58 @@
<div class="arc-create-page">
<div class="page-header">
<h1>Créer un nouvel arc narratif</h1>
<h1>{{ 'arcCreate.title' | translate }}</h1>
</div>
<form [formGroup]="form" (ngSubmit)="submit()" class="arc-form">
<div class="field">
<label for="arc-create-name">Nom de l'arc *</label>
<label for="arc-create-name">{{ 'arcCreate.nameLabel' | translate }}</label>
<input
id="arc-create-name"
type="text"
formControlName="name"
placeholder="Ex: L'Ombre du Nord"
[placeholder]="'arcCreate.namePlaceholder' | translate"
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
/>
</div>
<div class="field">
<label>Structure de l'arc</label>
<label>{{ 'arcCreate.structureLabel' | translate }}</label>
<div class="arc-type-choice">
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'LINEAR'">
<input type="radio" formControlName="type" value="LINEAR" />
<span class="arc-type-title">Linéaire</span>
<span class="arc-type-desc">Chapitres joués dans l'ordre — narration séquentielle classique.</span>
<span class="arc-type-title">{{ 'arcCreate.linearTitle' | translate }}</span>
<span class="arc-type-desc">{{ 'arcCreate.linearDesc' | translate }}</span>
</label>
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'HUB'">
<input type="radio" formControlName="type" value="HUB" />
<span class="arc-type-title">Hub</span>
<span class="arc-type-desc">Quêtes parallèles débloquées par des conditions — type sandbox (ex : Phandalin / Icespire Peak).</span>
<span class="arc-type-title">{{ 'arcCreate.hubTitle' | translate }}</span>
<span class="arc-type-desc">{{ 'arcCreate.hubDesc' | translate }}</span>
</label>
</div>
</div>
<div class="field">
<label for="arc-create-description">Description</label>
<label for="arc-create-description">{{ 'common.description' | translate }}</label>
<textarea
id="arc-create-description"
formControlName="description"
placeholder="Décrivez l'arc narratif principal..."
[placeholder]="'arcCreate.descriptionPlaceholder' | translate"
rows="5">
</textarea>
</div>
<div class="field">
<label>Icône</label>
<label>{{ 'arcCreate.iconLabel' | translate }}</label>
<app-icon-picker [options]="campaignIconOptions" [(selected)]="selectedIcon"></app-icon-picker>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary" [disabled]="form.invalid">
Créer l'arc
{{ 'arcCreate.submit' | translate }}
</button>
<button type="button" class="btn-secondary" (click)="cancel()">Annuler</button>
<button type="button" class="btn-secondary" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
</div>
</form>

View File

@@ -4,6 +4,7 @@ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angula
import { ActivatedRoute, Router } from '@angular/router';
import { forkJoin } from 'rxjs';
import { LucideAngularModule, BookOpen } from 'lucide-angular';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { CampaignService } from '../../../services/campaign.service';
import { CharacterService } from '../../../services/character.service';
import { NpcService } from '../../../services/npc.service';
@@ -21,7 +22,7 @@ import { CAMPAIGN_ICON_OPTIONS } from '../../campaign-icons';
*/
@Component({
selector: 'app-arc-create',
imports: [ReactiveFormsModule, LucideAngularModule, IconPickerComponent],
imports: [ReactiveFormsModule, LucideAngularModule, IconPickerComponent, TranslatePipe],
templateUrl: './arc-create.component.html',
styleUrls: ['./arc-create.component.scss']
})
@@ -43,7 +44,8 @@ export class ArcCreateComponent implements OnInit, OnDestroy {
private npcService: NpcService,
private randomTableService: RandomTableService,
private enemyService: EnemyService,
private layoutService: LayoutService
private layoutService: LayoutService,
private translate: TranslateService
) {
this.form = this.fb.group({
name: ['', Validators.required],
@@ -66,7 +68,7 @@ export class ArcCreateComponent implements OnInit, OnDestroy {
}).subscribe(({ campaign, allCampaigns, treeData }) => {
this.existingArcCount = treeData.arcs.length;
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId));
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId, this.translate));
});
}

View File

@@ -2,24 +2,24 @@
<div class="page-header">
<div>
<h1>{{ arc?.name || 'Arc' }}</h1>
<p class="subtitle">Arc narratif</p>
<h1>{{ arc?.name || ('arcEdit.fallbackTitle' | translate) }}</h1>
<p class="subtitle">{{ 'arcEdit.subtitle' | translate }}</p>
</div>
<div class="header-actions">
<button type="button" class="btn-ai"
(click)="toggleChat()"
[class.active]="chatOpen"
title="Ouvrir l'Assistant IA pour dialoguer autour de cet arc">
[title]="'arcEdit.aiAssistantTitle' | translate">
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
Assistant IA
{{ 'arcEdit.aiAssistant' | translate }}
</button>
<button type="button" class="btn-secondary" (click)="cancel()">Annuler</button>
<button type="button" class="btn-secondary" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
<button type="button" class="btn-danger" (click)="delete()">
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
Supprimer
{{ 'common.delete' | translate }}
</button>
<button type="button" class="btn-primary" (click)="submit()" [disabled]="form.invalid">
Sauvegarder
{{ 'common.save' | translate }}
</button>
</div>
</div>
@@ -28,118 +28,118 @@
<!-- Illustrations (galerie editable, rendu editorial) -->
<div class="field">
<label>Illustrations</label>
<label>{{ 'arcEdit.illustrationsLabel' | translate }}</label>
<app-image-gallery
[imageIds]="illustrationImageIds"
[editable]="true"
[layout]="'EDITORIAL'"
(imageIdsChange)="illustrationImageIds = $event">
</app-image-gallery>
<small class="field-hint">Ambiances, portraits, visuels evocateurs de l'arc. JPEG, PNG, WebP ou GIF, 10 Mo max.</small>
<small class="field-hint">{{ 'arcEdit.illustrationsHint' | translate }}</small>
</div>
<!-- Cartes & plans -->
<div class="field">
<label>Cartes &amp; plans</label>
<label>{{ 'arcEdit.mapsLabel' | translate }}</label>
<app-image-gallery
[imageIds]="mapImageIds"
[editable]="true"
[layout]="'MAPS'"
(imageIdsChange)="mapImageIds = $event">
</app-image-gallery>
<small class="field-hint">Cartes regionales et plans utiles aux joueurs pour situer l'action.</small>
<small class="field-hint">{{ 'arcEdit.mapsHint' | translate }}</small>
</div>
<div class="field">
<label for="arc-edit-name">Titre de l'arc *</label>
<label for="arc-edit-name">{{ 'arcEdit.nameLabel' | translate }}</label>
<input
id="arc-edit-name"
type="text"
formControlName="name"
placeholder="Ex: L'Ombre du Nord"
[placeholder]="'arcEdit.namePlaceholder' | translate"
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
/>
</div>
<div class="field">
<label for="arc-edit-description">Synopsis de l'arc</label>
<label for="arc-edit-description">{{ 'arcEdit.synopsisLabel' | translate }}</label>
<textarea
id="arc-edit-description"
formControlName="description"
placeholder="Décrivez l'histoire principale de cet arc narratif..."
[placeholder]="'arcEdit.synopsisPlaceholder' | translate"
rows="5">
</textarea>
</div>
<div class="field">
<label>Structure de l'arc</label>
<label>{{ 'arcEdit.structureLabel' | translate }}</label>
<div class="arc-type-choice">
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'LINEAR'">
<input type="radio" formControlName="type" value="LINEAR" />
<span class="arc-type-title">Linéaire</span>
<span class="arc-type-desc">Chapitres joués dans l'ordre — narration séquentielle.</span>
<span class="arc-type-title">{{ 'arcEdit.linearTitle' | translate }}</span>
<span class="arc-type-desc">{{ 'arcEdit.linearDesc' | translate }}</span>
</label>
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'HUB'">
<input type="radio" formControlName="type" value="HUB" />
<span class="arc-type-title">Hub</span>
<span class="arc-type-desc">Quêtes parallèles débloquées par des conditions (sandbox).</span>
<span class="arc-type-title">{{ 'arcEdit.hubTitle' | translate }}</span>
<span class="arc-type-desc">{{ 'arcEdit.hubDesc' | translate }}</span>
</label>
</div>
</div>
<div class="field">
<label>Icône</label>
<label>{{ 'arcEdit.iconLabel' | translate }}</label>
<app-icon-picker [options]="campaignIconOptions" [(selected)]="selectedIcon"></app-icon-picker>
</div>
<div class="field-row">
<div class="field">
<label for="arc-edit-themes">Thèmes principaux</label>
<label for="arc-edit-themes">{{ 'arcEdit.themesLabel' | translate }}</label>
<textarea
id="arc-edit-themes"
formControlName="themes"
placeholder="Quels sont les thèmes explorés dans cet arc ? (trahison, rédemption...)"
[placeholder]="'arcEdit.themesPlaceholder' | translate"
rows="4">
</textarea>
</div>
<div class="field">
<label for="arc-edit-stakes">Enjeux globaux</label>
<label for="arc-edit-stakes">{{ 'arcEdit.stakesLabel' | translate }}</label>
<textarea
id="arc-edit-stakes"
formControlName="stakes"
placeholder="Quels sont les enjeux majeurs de cet arc pour les personnages ?"
[placeholder]="'arcEdit.stakesPlaceholder' | translate"
rows="4">
</textarea>
</div>
</div>
<div class="field">
<label for="arc-edit-gm-notes">Notes et planification du MJ</label>
<label for="arc-edit-gm-notes">{{ 'arcEdit.gmNotesLabel' | translate }}</label>
<textarea
id="arc-edit-gm-notes"
formControlName="gmNotes"
placeholder="Vos notes sur la direction de l'arc, les twists prévus, les révélations importantes..."
[placeholder]="'arcEdit.gmNotesPlaceholder' | translate"
rows="5">
</textarea>
<small class="field-hint">Ces notes sont privées et ne seront pas exportées vers FoundryVTT.</small>
<small class="field-hint">{{ 'arcEdit.gmNotesHint' | translate }}</small>
</div>
<div class="field">
<label for="arc-edit-rewards">Récompenses et progression</label>
<label for="arc-edit-rewards">{{ 'arcEdit.rewardsLabel' | translate }}</label>
<textarea
id="arc-edit-rewards"
formControlName="rewards"
placeholder="Quelles récompenses les joueurs obtiendront-ils ? Objets, niveaux, connaissances, contacts..."
[placeholder]="'arcEdit.rewardsPlaceholder' | translate"
rows="4">
</textarea>
</div>
<div class="field">
<label for="arc-edit-resolution">Dénouement prévu</label>
<label for="arc-edit-resolution">{{ 'arcEdit.resolutionLabel' | translate }}</label>
<textarea
id="arc-edit-resolution"
formControlName="resolution"
placeholder="Comment cet arc devrait-il se terminer ? Quelles sont les issues possibles ?"
[placeholder]="'arcEdit.resolutionPlaceholder' | translate"
rows="4">
</textarea>
</div>
@@ -147,7 +147,7 @@
<!-- ===== Pages Lore associées (phase B2 cross-context) ===== -->
@if (loreId) {
<div class="field">
<label>Pages Lore associées</label>
<label>{{ 'arcEdit.relatedPagesLabel' | translate }}</label>
<app-lore-link-picker
[value]="relatedPageIds"
[availablePages]="availablePages"
@@ -155,7 +155,7 @@
(valueChange)="relatedPageIds = $event">
</app-lore-link-picker>
<small class="field-hint">
Liez cet arc à des PNJ, lieux ou éléments du Lore. Cliquez sur un chip pour ouvrir la page associée.
{{ 'arcEdit.relatedPagesHint' | translate }}
</small>
</div>
}
@@ -163,8 +163,7 @@
@if (!loreId) {
<div class="field lore-hint">
<small class="field-hint">
💡 Cette campagne n'est associée à aucun univers. Associez-la à un Lore dans l'écran de la campagne
pour pouvoir lier cet arc à des pages du Lore (PNJ, lieux, etc.).
{{ 'arcEdit.noLoreHint' | translate }}
</small>
</div>
}
@@ -179,7 +178,7 @@
entityType="arc"
[entityId]="arcId"
[isOpen]="chatOpen"
welcomeMessage="Je vois cet arc. Demande-moi d'enrichir ses thèmes, ses enjeux ou son dénouement."
[welcomeMessage]="'arcEdit.chatWelcome' | translate"
[quickSuggestions]="chatQuickSuggestions"
(close)="chatOpen = false">
</app-ai-chat-drawer>

View File

@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { forkJoin, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { CampaignService } from '../../../services/campaign.service';
import { CharacterService } from '../../../services/character.service';
import { NpcService } from '../../../services/npc.service';
@@ -34,7 +35,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
*/
@Component({
selector: 'app-arc-edit',
imports: [ReactiveFormsModule, LucideAngularModule, LoreLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent],
imports: [ReactiveFormsModule, LucideAngularModule, LoreLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent, TranslatePipe],
templateUrl: './arc-edit.component.html',
styleUrls: ['./arc-edit.component.scss']
})
@@ -46,11 +47,13 @@ export class ArcEditComponent implements OnInit, OnDestroy {
/** État drawer chat IA (b5.7 — intégration Campagne). */
chatOpen = false;
readonly chatQuickSuggestions = [
'Propose 3 thèmes majeurs pour cet arc',
'Imagine des enjeux qui mettent la pression sur les joueurs',
'Suggère un dénouement en deux actes'
];
get chatQuickSuggestions(): string[] {
return [
this.translate.instant('arcEdit.chatSuggestion1'),
this.translate.instant('arcEdit.chatSuggestion2'),
this.translate.instant('arcEdit.chatSuggestion3')
];
}
toggleChat(): void { this.chatOpen = !this.chatOpen; }
@@ -83,7 +86,8 @@ export class ArcEditComponent implements OnInit, OnDestroy {
private pageService: PageService,
private layoutService: LayoutService,
private pageTitleService: PageTitleService,
private confirmDialog: ConfirmDialogService
private confirmDialog: ConfirmDialogService,
private translate: TranslateService
) {
this.form = this.fb.group({
name: ['', Validators.required],
@@ -149,7 +153,7 @@ export class ArcEditComponent implements OnInit, OnDestroy {
resolution: arc.resolution ?? ''
});
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId));
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId, this.translate));
});
}
@@ -178,10 +182,10 @@ export class ArcEditComponent implements OnInit, OnDestroy {
delete(): void {
this.confirmDialog.confirm({
title: 'Supprimer l\'arc',
message: `Supprimer l'arc "${this.arc?.name}" ?`,
details: ['Cette action est irréversible.'],
confirmLabel: 'Supprimer',
title: this.translate.instant('arcEdit.deleteTitle'),
message: this.translate.instant('arcEdit.deleteMessage', { name: this.arc?.name }),
details: [this.translate.instant('arcEdit.irreversible')],
confirmLabel: this.translate.instant('common.delete'),
variant: 'danger'
}).then(ok => {
if (!ok) return;

View File

@@ -9,17 +9,17 @@
{{ arc.name }}
</h1>
<p class="view-subtitle">
{{ arc.type === 'HUB' ? 'Arc en hub (quêtes non linéaires)' : 'Arc narratif' }}
{{ (arc.type === 'HUB' ? 'arcView.subtitleHub' : 'arcView.subtitleLinear') | translate }}
</p>
</div>
<div class="view-actions">
<button type="button" class="btn-primary" (click)="editMode()">
<lucide-icon [img]="Pencil" [size]="14"></lucide-icon>
Modifier
{{ 'common.edit' | translate }}
</button>
<button type="button" class="btn-danger" (click)="deleteArc()" title="Supprimer l'arc et tout son contenu">
<button type="button" class="btn-danger" (click)="deleteArc()" [title]="'arcView.deleteTitle' | translate">
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
Supprimer
{{ 'common.delete' | translate }}
</button>
</div>
</header>
@@ -32,7 +32,7 @@
<!-- Cartes & plans -->
@if ((arc.mapImageIds?.length ?? 0) > 0) {
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> Cartes & plans</h2>
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> {{ 'arcView.mapsTitle' | translate }}</h2>
<app-image-gallery [imageIds]="arc.mapImageIds ?? []" [layout]="'MAPS'"></app-image-gallery>
</section>
}
@@ -40,10 +40,10 @@
Le statut effectif et les actions de jeu vivent dans l'écran d'une Partie. -->
@if (arc.type === 'HUB') {
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> Quêtes du hub (scénario)</h2>
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> {{ 'arcView.hubQuestsTitle' | translate }}</h2>
@if (hubQuests.length === 0) {
<p class="view-section-empty">
Aucune quête pour ce hub. Créez-en une pour démarrer.
{{ 'arcView.hubQuestsEmpty' | translate }}
</p>
}
@if (hubQuests.length > 0) {
@@ -66,7 +66,7 @@
@if ((q.prerequisites?.length ?? 0) > 0) {
<div class="hub-quest-card-locked-hint">
<lucide-icon [img]="AlertCircle" [size]="13"></lucide-icon>
<span>{{ q.prerequisites!.length }} condition(s) de déblocage</span>
<span>{{ 'arcView.unlockConditions' | translate:{ n: q.prerequisites!.length } }}</span>
<ul class="hub-quest-card-prereq-list">
@for (p of q.prerequisites; track p) {
<li>{{ describePrerequisite(p) }}</li>
@@ -81,45 +81,45 @@
</section>
}
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">📜</span> Synopsis</h2>
<h2 class="view-section-title"><span class="view-section-icon">📜</span> {{ 'arcView.synopsisTitle' | translate }}</h2>
@if (arc.description?.trim()) {
<p class="view-section-body">{{ arc.description }}</p>
} @else {
<p class="view-section-empty">Non renseigné</p>
<p class="view-section-empty">{{ 'arcView.notProvided' | translate }}</p>
}
</section>
<div class="view-row">
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon"></span> Thèmes principaux</h2>
<h2 class="view-section-title"><span class="view-section-icon"></span> {{ 'arcView.themesTitle' | translate }}</h2>
@if (arc.themes?.trim()) {
<p class="view-section-body">{{ arc.themes }}</p>
} @else {
<p class="view-section-empty">Non renseigné</p>
<p class="view-section-empty">{{ 'arcView.notProvided' | translate }}</p>
}
</section>
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">⚖️</span> Enjeux globaux</h2>
<h2 class="view-section-title"><span class="view-section-icon">⚖️</span> {{ 'arcView.stakesTitle' | translate }}</h2>
@if (arc.stakes?.trim()) {
<p class="view-section-body">{{ arc.stakes }}</p>
} @else {
<p class="view-section-empty">Non renseigné</p>
<p class="view-section-empty">{{ 'arcView.notProvided' | translate }}</p>
}
</section>
</div>
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">🎁</span> Récompenses et progression</h2>
<h2 class="view-section-title"><span class="view-section-icon">🎁</span> {{ 'arcView.rewardsTitle' | translate }}</h2>
@if (arc.rewards?.trim()) {
<p class="view-section-body">{{ arc.rewards }}</p>
} @else {
<p class="view-section-empty">Non renseigné</p>
<p class="view-section-empty">{{ 'arcView.notProvided' | translate }}</p>
}
</section>
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">🎬</span> Dénouement prévu</h2>
<h2 class="view-section-title"><span class="view-section-icon">🎬</span> {{ 'arcView.resolutionTitle' | translate }}</h2>
@if (arc.resolution?.trim()) {
<p class="view-section-body">{{ arc.resolution }}</p>
} @else {
<p class="view-section-empty">Non renseigné</p>
<p class="view-section-empty">{{ 'arcView.notProvided' | translate }}</p>
}
</section>
<!-- Notes MJ (bloc privé rouge discret) -->
@@ -127,7 +127,7 @@
<section class="view-section view-section--private">
<h2 class="view-section-title">
<span class="view-section-icon">🔒</span>
Notes et planification du MJ
{{ 'arcView.gmNotesTitle' | translate }}
</h2>
<p class="view-section-body">{{ arc.gmNotes }}</p>
</section>
@@ -135,7 +135,7 @@
<!-- Pages Lore liées (chips cliquables) -->
@if (loreId && (arc.relatedPageIds?.length ?? 0) > 0) {
<section class="view-section">
<h2 class="view-section-title"><span class="view-section-icon">🔗</span> Pages Lore associées</h2>
<h2 class="view-section-title"><span class="view-section-icon">🔗</span> {{ 'arcView.relatedPagesTitle' | translate }}</h2>
<div class="view-chips">
@for (relId of arc.relatedPageIds; track relId) {
<a class="view-chip"

View File

@@ -4,6 +4,7 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { forkJoin, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { LucideAngularModule, Pencil, Trash2, AlertCircle } from 'lucide-angular';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { resolveCampaignIcon } from '../../campaign-icons';
import { CampaignService } from '../../../services/campaign.service';
import { CharacterService } from '../../../services/character.service';
@@ -26,7 +27,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
*/
@Component({
selector: 'app-arc-view',
imports: [RouterModule, LucideAngularModule, ImageGalleryComponent],
imports: [RouterModule, LucideAngularModule, ImageGalleryComponent, TranslatePipe],
templateUrl: './arc-view.component.html',
styleUrls: ['./arc-view.component.scss']
})
@@ -65,7 +66,8 @@ export class ArcViewComponent implements OnInit, OnDestroy {
private pageService: PageService,
private layoutService: LayoutService,
private pageTitleService: PageTitleService,
private confirmDialog: ConfirmDialogService
private confirmDialog: ConfirmDialogService,
private translate: TranslateService
) {}
ngOnInit(): void {
@@ -111,7 +113,7 @@ export class ArcViewComponent implements OnInit, OnDestroy {
list.forEach(c => { if (c.id) this.allChaptersById[c.id] = c; })
);
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId));
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId, this.translate));
});
}
@@ -119,11 +121,11 @@ export class ArcViewComponent implements OnInit, OnDestroy {
describePrerequisite(p: Prerequisite): string {
switch (p.kind) {
case 'QUEST_COMPLETED':
return `Quête « ${this.allChaptersById[p.questId]?.name ?? '?'} » terminée`;
return this.translate.instant('arcView.prereqQuestCompleted', { name: this.allChaptersById[p.questId]?.name ?? '?' });
case 'SESSION_REACHED':
return `Session ${p.minSessionNumber} atteinte`;
return this.translate.instant('arcView.prereqSessionReached', { n: p.minSessionNumber });
case 'FLAG_SET':
return `Fait : ${p.flagName}`;
return this.translate.instant('arcView.prereqFlagSet', { flag: p.flagName });
}
}
@@ -135,7 +137,7 @@ export class ArcViewComponent implements OnInit, OnDestroy {
}
titleOfRelated(pageId: string): string {
return this.availablePages.find(p => p.id === pageId)?.title ?? '(page supprimée)';
return this.availablePages.find(p => p.id === pageId)?.title ?? this.translate.instant('arcView.deletedPage');
}
editMode(): void {
@@ -153,20 +155,24 @@ export class ArcViewComponent implements OnInit, OnDestroy {
this.campaignService.getArcDeletionImpact(arc.id!).subscribe({
next: impact => {
const parts: string[] = [];
if (impact.chapters > 0) parts.push(`${impact.chapters} chapitre${impact.chapters > 1 ? 's' : ''}`);
if (impact.scenes > 0) parts.push(`${impact.scenes} scène${impact.scenes > 1 ? 's' : ''}`);
if (impact.chapters > 0) {
parts.push(this.translate.instant(impact.chapters > 1 ? 'arcView.chaptersPlural' : 'arcView.chaptersSingular', { n: impact.chapters }));
}
if (impact.scenes > 0) {
parts.push(this.translate.instant(impact.scenes > 1 ? 'arcView.scenesPlural' : 'arcView.scenesSingular', { n: impact.scenes }));
}
const details: string[] = [];
if (parts.length) {
details.push(`Cette action supprimera aussi : ${parts.join(', ')}.`);
details.push(this.translate.instant('arcView.deleteCascade', { parts: parts.join(', ') }));
}
details.push('Cette action est irréversible.');
details.push(this.translate.instant('arcView.irreversible'));
this.confirmDialog.confirm({
title: 'Supprimer l\'arc',
message: `Supprimer l'arc "${arc.name}" ?`,
title: this.translate.instant('arcView.deleteConfirmTitle'),
message: this.translate.instant('arcView.deleteConfirmMessage', { name: arc.name }),
details,
confirmLabel: 'Supprimer',
confirmLabel: this.translate.instant('common.delete'),
variant: 'danger'
}).then(ok => {
if (!ok) return;