diff --git a/doc/ArchitectureOfTheDocumentationPipeline.en.md b/doc/ArchitectureOfTheDocumentationPipeline.en.md
index b9c236d..35972e7 100644
--- a/doc/ArchitectureOfTheDocumentationPipeline.en.md
+++ b/doc/ArchitectureOfTheDocumentationPipeline.en.md
@@ -3,183 +3,179 @@
đ **Languages:**
đŹđ§ English (this file) | đ«đ· [Français](./ArchitectureOfTheDocumentationPipeline.fr.md)
-FirstClassErrors does not treat documentation as an external artifact.
-Documentation is derived directly from the code and flows through a structured pipeline.
-
-The pipeline separates **knowledge definition**, **extraction**, and **rendering**.
-
-## đ§± 1. Knowledge lives in the code
-
-Error knowledge is written where errors are defined:
+FirstClassErrors derives error documentation from the code that defines and creates errors. The pipeline keeps five responsibilities separate: defining knowledge, extracting it, isolating execution, aggregating a catalog, and rendering files.
+
+## The pipeline at a glance
+
+```mermaid
+flowchart LR
+ A[Error factories and DescribeError DSL]
+ B[Assembly extraction]
+ C[Isolated worker process]
+ D[Solution aggregation]
+ E[Renderer]
+ F[Markdown, HTML, JSON, or custom output]
+
+ A --> B
+ B --> C
+ C --> D
+ D --> E
+ E --> F
+```
-* A static class annotated with `[ProvidesErrorsFor(...)]` groups the errors that belong to a given model
-* `Error` subtypes (`DomainError`, `PrimaryPortError`, `SecondaryPortError`, ...) represent categories of errors
-* Factory methods represent specific error situations
-* The `DescribeError` DSL describes meaning, rules, diagnostics, and examples
+The important boundary is this:
-At this stage, documentation is **structured data**, not text files.
+> application code defines structured error knowledge; renderers only decide how that knowledge is presented.
-## đ 2. Errors are anchored and linked to documentation
+## 1. Knowledge is defined next to the error
-A static class declares that it owns the errors of a given model:
+A static class groups the errors associated with one source:
```csharp
[ProvidesErrorsFor(nameof(Temperature))]
-public static class InvalidTemperatureError { ... }
+public static class InvalidTemperatureError {
+ // factories, documentation methods, and codes
+}
```
-This attribute is the primary anchor of the documentation model: it marks the class as a source of errors and supplies `ErrorDocumentation.Source` (the model name passed via `nameof(...)`). It can also carry an optional `Description`, rendered as an introduction to that source's group in the generated documentation:
+Each factory represents one recognized error situation. `[DocumentedBy]` links that factory to a documentation method:
```csharp
-[ProvidesErrorsFor(nameof(Temperature),
- Description = "Errors raised when constructing a Temperature value from an out-of-range input.")]
+[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))]
+internal static DomainError BelowAbsoluteZero(decimal value) {
+ // create the Error
+}
```
-The `Description` is literal text by default; set `DescriptionResourceType` to have it resolved as a resource key instead, for localization (see [Internationalization](Internationalization.en.md)).
-
-Inside that class, each factory method is linked to its documentation method using:
-
```csharp
-[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))]
+private static ErrorDocumentation BelowAbsoluteZeroDocumentation() {
+ return DescribeError
+ .WithTitle("Temperature below absolute zero")
+ .WithDescription("This error occurs when a temperature is below the physical minimum.")
+ .WithRule("A temperature cannot be lower than absolute zero.")
+ .WithDiagnostic(
+ "A converted or computed temperature fell below the physical minimum.",
+ ErrorOrigin.Internal,
+ "Check the computation or conversion that produced the value.")
+ .WithExamples(() => BelowAbsoluteZero(-1m));
+}
```
-This creates an explicit connection between:
+At this point there is no Markdown, HTML, or JSON. There is structured knowledge expressed in code.
-* how an error is created
-* how it is described
+## 2. Extraction turns executable documentation into data
-## đ 3. Extraction
+The extractor finds `[ProvidesErrorsFor]` classes, resolves `[DocumentedBy]` links, and invokes the documentation methods and their example factories.
-`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` scans a single assembly and:
+This matters because examples are produced by the real factory code. They are not copied strings that can silently drift from runtime behavior.
-* finds any class annotated with `[ProvidesErrorsFor(...)]` (these are plain static classes, not exception types)
-* finds factory methods marked with `[DocumentedBy]`
-* **invokes** the linked documentation methods â and the example factories they reference. Documentation is *executable*, so the examples reflect the real code rather than a copy that can drift. A factory that throws, or a `[DocumentedBy]` reference that cannot be resolved, is recorded as a failure instead of aborting the whole scan.
-* returns an `ErrorDocumentationExtractionResult`: the `ErrorDocumentation` collection (deduped by `Code`, ordered by `Code`) together with the list of extraction `Failures`
+The result is an in-memory catalog of `ErrorDocumentation` objects plus any extraction failures.
-At this stage, documentation becomes a structured in-memory model.
+## 3. Workers isolate target execution
-## đ§Ș 4. Extraction runs out of process
+Extraction executes application code. For that reason, solution-level generation does not load every target assembly into one long-lived process. It starts a short-lived worker for each assembly.
-Because extraction **executes** the target's code, each assembly is documented by a short-lived **worker process**, spawned by the generator (`dotnet exec`, using the target's own dependency file). This buys:
+That isolation provides:
-* **living examples** â the example factories run against the real code, not a stale description
-* a **fresh static registry** per assembly â no state leaks from one assembly to the next
-* **version isolation** â each target binds its own FirstClassErrors version
-* **fault isolation** â a crashing or hanging assembly is killed on a timeout and recorded as a failure, without taking the whole run down
+- a fresh static state for each assembly;
+- dependency and FirstClassErrors version isolation;
+- containment of crashes and hangs;
+- a clear timeout boundary;
+- failure reporting without necessarily losing the whole run.
-The worker writes its `ErrorDocumentationExtractionResult` as JSON; the generator reads it back and moves on to the next assembly.
+The worker serializes the extraction result to JSON, then the generator continues with the next assembly.
-## đ§© 5. Aggregation at solution level
+For exact discovery, opt-in, timeout, and failure-policy rules, see [Extraction and Project Discovery Reference](DocumentationExtractionReference.en.md).
-`SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(solutionPath[, options])` â or `GetErrorDocumentationFromAssemblies(paths, options)` for pre-built binaries â works at a higher level and:
+## 4. The generator builds one catalog
-* discovers the projects (via `dotnet sln list`), keeps the ones that opt in (see below), and â unless told not to â builds them
-* runs a worker for each output assembly
-* aggregates all extracted `ErrorDocumentation` (deduped by `Code`, ordered by `Code`)
+At solution level, the generator:
-This produces a **global error catalog** for the application or system.
+1. builds the solution unless `--no-build` is used;
+2. discovers the projects that participate in documentation generation;
+3. starts one worker per selected output assembly;
+4. collects documentation and extraction failures;
+5. deduplicates and orders errors by code.
-### Opting a project in
+The output of this stage is one global catalog for the selected application or set of assemblies.
-Solution-level generation is **opt-in per project**: a project is documented only when its project file (`.csproj`) sets the MSBuild property
+The CLI exposes the common path:
-```xml
-
- true
-
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --format markdown \
+ --layout split \
+ --service-name my-api \
+ --output ./docs/errors
```
-Each project discovered in the solution is then treated as follows:
-
-| `GenerateErrorDocumentation` | Result |
-| ---------------------------- | ------ |
-| `true` | documented |
-| absent | skipped â the default is opt-in |
-| `false` | always skipped, even when the "include everything" policy is on |
-| declared twice, or `Condition`-gated | reported, never guessed â a warning that skips the project (`Continue`), or a hard error |
+## 5. Renderers turn the catalog into files
-This keeps the catalog â and the worker processes spawned to build it â scoped to the projects that actually define application errors, rather than every project in the solution.
-
-The property is a **marker read straight from the project file**, not an MSBuild build switch: nothing consumes it at plain `dotnet build` time, and passing `-p:GenerateErrorDocumentation=âŠ` on a build command line has no effect. Because it is read from the project XML rather than evaluated by MSBuild, it must be declared literally in the `.csproj`: a value inherited from a shared `Directory.Build.props` or brought in by an import is not seen, so the project is treated as if the marker were absent. If the marker *is* in the `.csproj` but its effective value is unknowable from the XML alone â declared more than once, or gated behind a `Condition` â GenDoc does not guess: it reports the project through the configured failure behavior (a warning that skips it under `Continue`, a hard error otherwise). The `--assemblies` path is not subject to this filter â it documents exactly the binaries you name.
-
-> For programmatic callers, `SolutionGenerationOptions` exposes `OptInPropertyName` (rename the marker) and `IncludeProjectsWithoutOptIn` (document every project regardless). The `fce` CLI uses the defaults shown above.
-
-## đšïž 6. Rendering to output formats
-
-A renderer turns the in-memory catalog into published documentation. Because the model is plain data, rendering is decoupled behind a single contract:
+A renderer receives the structured catalog and a `RenderRequest`. It returns one or more `RenderedDocument` values.
```csharp
public interface IErrorDocumentationRenderer {
string Format { get; }
IReadOnlyCollection SupportedLayouts { get; }
- IReadOnlyList Render(IEnumerable catalog, RenderRequest request);
+ IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request);
}
```
-Each renderer declares the layouts it can produce and is asked for one per call through the `RenderRequest` (which also carries the target culture); an unsupported layout is rejected with a `LayoutNotSupportedException`. Three renderers ship in the box:
+Built-in renderers currently provide:
-* **json** â a curated, stable JSON schema (`single` layout only)
-* **markdown** â a single file, or (with `--layout split`) a README index plus one file per source group and one file per error (`single`/`split`)
-* **html** â a self-contained static site: a searchable table of contents grouped by source and, in `split`, one page per error (`single`/`split`). See [The HTML renderer](TheHtmlRenderer.en.md).
+| Format | Purpose | Layouts |
+| --- | --- | --- |
+| `json` | stable machine-readable catalog | `single` |
+| `markdown` | repository or portal documentation | `single`, `split` |
+| `html` | self-contained searchable static documentation | `single`, `split` |
-Any other format (CSV, a company template, âŠ) is a **custom renderer**: implement the interface and register it. See [Writing a custom renderer](WritingACustomRenderer.en.md).
+`single` produces one document; `split` produces one page per error.
-## đ§° 7. CLI orchestration
+Custom renderers use the same contract. See [Writing a custom renderer](WritingACustomRenderer.en.md).
-The `fce` CLI orchestrates the whole process:
+## 6. Culture crosses two distinct boundaries
-```bash
-fce generate --solution ./MyApp.sln --format markdown --layout split --output ./docs/errors
-```
+Internationalization is deliberately split:
-It handles the solution build, extraction (via workers), aggregation and rendering. Common options can be stored in a configuration file (`fce.json`) so they need not be repeated, and custom renderers are referenced there too:
+- **extraction culture** localizes error content produced by factories and documentation methods;
+- **render culture** localizes headings, labels, and other renderer-owned boilerplate.
-```bash
-fce config init
-fce config renderer add ./plugins/MyCompany.Renderers.dll
-fce generate # uses the configured solution, format, output, renderersâŠ
-```
-
-A value passed on the command line overrides the configuration.
-
-## đ 8. Internationalization
+Stable identifiers remain culture-invariant: codes, source identities, context-key names, generated paths, anchors, and internal diagnostic messages.
-The pipeline is culture-aware at two levels: the extractor localizes error *content* (under the requested UI culture) and each renderer localizes its own *templates* (from `RenderRequest.Culture`), while file names and anchors stay culture-invariant so links never break. It is opt-in and driven by `fce generate --language`.
+See [Internationalization](Internationalization.en.md) for the complete workflow.
-See **[Internationalization](Internationalization.en.md)** for the full story â choosing the language, the `DescriptionResourceType` hook, localizing renderer templates, and driving it without the CLI.
+## Why the separation matters
-## đ Why this architecture matters
+| Component | Owns |
+| --- | --- |
+| application code | error meaning, rules, diagnostics, examples, public messages |
+| extractor | discovery and execution of documented factories |
+| worker | process and dependency isolation |
+| generator | build, selection, aggregation, ordering, failure collection |
+| renderer | file format, layout, template text |
+| CLI | orchestration and configuration |
-This separation ensures:
+This prevents several forms of coupling:
-| Layer | Responsibility |
-| --------- | ------------------------------------- |
-| Code | Define error knowledge |
-| Reader | Extract structured documentation |
-| Worker | Execute the code in isolation |
-| Generator | Build and aggregate across assemblies |
-| Renderer | Turn the catalog into a target format |
-| CLI | Orchestrate the process |
+- factories do not know whether the output is Markdown or HTML;
+- renderers do not execute application factories;
+- one failing assembly does not have to corrupt every other extraction;
+- localized content and localized presentation remain independent;
+- programmatic callers can use individual pipeline stages without the CLI.
-Documentation remains:
+## The key idea
-* close to the code
-* always up to date
-* structured
-* tool-friendly
+> Error documentation is not manually rewritten from the system. It is derived from the same factories and structured descriptions that define the system's recognized failures.
-## đŻ The key idea
-
-> Error documentation is not written *about* the system.
-> It is derived *from* the system.
-
-The code is the source of truth.
+The code remains the source of truth; the pipeline makes that knowledge portable.
---
----
+---
\ No newline at end of file
diff --git a/doc/ArchitectureOfTheDocumentationPipeline.fr.md b/doc/ArchitectureOfTheDocumentationPipeline.fr.md
index 79eb45e..e7370b4 100644
--- a/doc/ArchitectureOfTheDocumentationPipeline.fr.md
+++ b/doc/ArchitectureOfTheDocumentationPipeline.fr.md
@@ -1,185 +1,181 @@
# Architecture du pipeline de documentation
-đ **Langues:**
+đ **Langues :**
đŹđ§ [English](./ArchitectureOfTheDocumentationPipeline.en.md) | đ«đ· Français (ce fichier)
-FirstClassErrors ne considĂšre pas la documentation comme un artefact externe.
-La documentation est dérivée directement du code et circule à travers un pipeline structuré.
+FirstClassErrors dĂ©rive la documentation des erreurs du code qui les dĂ©finit et les crĂ©e. Le pipeline sĂ©pare cinq responsabilitĂ©s : dĂ©finir la connaissance, lâextraire, isoler son exĂ©cution, agrĂ©ger un catalogue et produire les fichiers.
-Le pipeline sĂ©pare la **dĂ©finition de la connaissance**, lâ**extraction** et le **rendu**.
+## Le pipeline en un coup dâĆil
-## đ§± 1. La connaissance vit dans le code
+```mermaid
+flowchart LR
+ A[Factories d'erreur et DSL DescribeError]
+ B[Extraction de l'assembly]
+ C[Processus worker isolé]
+ D[Agrégation de la solution]
+ E[Renderer]
+ F[Sortie Markdown, HTML, JSON ou personnalisée]
-La connaissance liĂ©e aux erreurs est Ă©crite Ă lâendroit oĂč les erreurs sont dĂ©finies :
+ A --> B
+ B --> C
+ C --> D
+ D --> E
+ E --> F
+```
-* Une classe statique annotée avec `[ProvidesErrorsFor(...)]` regroupe les erreurs liées à un modÚle donné
-* Les sous-types dâ`Error` (`DomainError`, `PrimaryPortError`, `SecondaryPortError`, ...) reprĂ©sentent des catĂ©gories dâerreurs
-* Les mĂ©thodes factory reprĂ©sentent des situations dâerreur spĂ©cifiques
-* Le DSL `DescribeError` décrit le sens, les rÚgles, les diagnostics et les exemples
+La frontiĂšre essentielle est la suivante :
-à ce stade, la documentation est une **donnée structurée**, pas des fichiers texte.
+> le code applicatif définit une connaissance structurée sur les erreurs ; les renderers décident uniquement de sa présentation.
-## đ 2. Les erreurs sont ancrĂ©es et liĂ©es Ă la documentation
+## 1. La connaissance est dĂ©finie Ă cĂŽtĂ© de lâerreur
-Une classe statique dĂ©clare quâelle possĂšde les erreurs dâun modĂšle donnĂ© :
+Une classe statique regroupe les erreurs associées à une source :
```csharp
[ProvidesErrorsFor(nameof(Temperature))]
-public static class InvalidTemperatureError { ... }
+public static class InvalidTemperatureError {
+ // factories, méthodes de documentation et codes
+}
```
-Cet attribut est le point dâancrage principal du modĂšle de documentation : il marque la classe comme source dâerreurs et fournit `ErrorDocumentation.Source` (le nom du modĂšle passĂ© via `nameof(...)`). Il peut aussi porter une `Description` optionnelle, rendue comme introduction au groupe de cette source dans la documentation gĂ©nĂ©rĂ©e :
+Chaque factory reprĂ©sente une situation dâerreur reconnue. `[DocumentedBy]` lie cette factory Ă une mĂ©thode de documentation :
```csharp
-[ProvidesErrorsFor(nameof(Temperature),
- Description = "Errors raised when constructing a Temperature value from an out-of-range input.")]
+[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))]
+internal static DomainError BelowAbsoluteZero(decimal value) {
+ // création de l'Error
+}
```
-La `Description` est un texte littĂ©ral par dĂ©faut ; renseignez `DescriptionResourceType` pour quâelle soit rĂ©solue comme une clĂ© de ressource Ă la place, en vue de la localisation (voir [Internationalisation](Internationalisation.fr.md)).
-
-Ă lâintĂ©rieur de cette classe, chaque mĂ©thode factory est liĂ©e Ă sa mĂ©thode de documentation via :
-
```csharp
-[DocumentedBy(nameof(BelowAbsoluteZeroDocumentation))]
+private static ErrorDocumentation BelowAbsoluteZeroDocumentation() {
+ return DescribeError
+ .WithTitle("Température sous le zéro absolu")
+ .WithDescription("Cette erreur survient lorsqu'une température est inférieure à la limite physique.")
+ .WithRule("Une tempĂ©rature ne peut pas ĂȘtre infĂ©rieure au zĂ©ro absolu.")
+ .WithDiagnostic(
+ "Une température convertie ou calculée est passée sous la limite physique.",
+ ErrorOrigin.Internal,
+ "Vérifiez le calcul ou la conversion qui a produit la valeur.")
+ .WithExamples(() => BelowAbsoluteZero(-1m));
+}
```
-Cela crée une connexion explicite entre :
+Ă ce stade, il nâexiste encore ni Markdown, ni HTML, ni JSON. Il existe une connaissance structurĂ©e exprimĂ©e dans le code.
-* la maniÚre dont une erreur est créée
-* la maniÚre dont elle est décrite
+## 2. Lâextraction transforme la documentation exĂ©cutable en donnĂ©es
-## đ 3. Extraction
+Lâextracteur trouve les classes `[ProvidesErrorsFor]`, rĂ©sout les liens `[DocumentedBy]`, puis invoque les mĂ©thodes de documentation et leurs factories dâexemples.
-`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` analyse un assembly et :
+Câest important parce que les exemples sont produits par le vrai code des factories. Ce ne sont pas des chaĂźnes copiĂ©es qui pourraient dĂ©river silencieusement du comportement runtime.
-* trouve toute classe annotĂ©e avec `[ProvidesErrorsFor(...)]` (ce sont de simples classes statiques, pas des types dâexception)
-* trouve les méthodes factory marquées avec `[DocumentedBy]`
-* **invoque** les mĂ©thodes de documentation liĂ©es â ainsi que les factories dâexemples quâelles rĂ©fĂ©rencent. La documentation est *exĂ©cutable* : les exemples reflĂštent le vrai code, et non une copie qui pourrait dĂ©river. Une factory qui lĂšve une exception, ou une rĂ©fĂ©rence `[DocumentedBy]` non rĂ©solvable, est enregistrĂ©e comme une *failure* au lieu dâinterrompre toute lâanalyse.
-* renvoie un `ErrorDocumentationExtractionResult` : la collection dâ`ErrorDocumentation` (dĂ©dupliquĂ©e par `Code`, ordonnĂ©e par `Code`) avec la liste des *failures* dâextraction
+Le rĂ©sultat est un catalogue en mĂ©moire dâobjets `ErrorDocumentation`, accompagnĂ© des Ă©ventuels Ă©checs dâextraction.
-à ce stade, la documentation devient un modÚle structuré en mémoire.
+## 3. Les workers isolent lâexĂ©cution des cibles
-## đ§Ș 4. Lâextraction sâexĂ©cute hors-processus
+Lâextraction exĂ©cute du code applicatif. Pour cette raison, la gĂ©nĂ©ration au niveau solution ne charge pas tous les assemblies cibles dans un unique processus longue durĂ©e. Elle dĂ©marre un worker Ă©phĂ©mĂšre pour chaque assembly.
-Parce que lâextraction **exĂ©cute** le code de la cible, chaque assembly est documentĂ© par un **processus worker** Ă©phĂ©mĂšre, lancĂ© par le gĂ©nĂ©rateur (`dotnet exec`, en sâappuyant sur le fichier de dĂ©pendances de la cible). On y gagne :
+Cette isolation apporte :
-* des **exemples vivants** â les factories dâexemples sâexĂ©cutent contre le vrai code, pas une description figĂ©e
-* un **registre statique neuf** par assembly â aucun Ă©tat ne fuit dâun assembly Ă lâautre
-* lâ**isolation des versions** â chaque cible lie sa propre version de FirstClassErrors
-* lâ**isolation des pannes** â un assembly qui plante ou se bloque est tuĂ© sur *timeout* et enregistrĂ© comme une *failure*, sans faire tomber toute la gĂ©nĂ©ration
+- un état statique neuf pour chaque assembly ;
+- lâisolation des dĂ©pendances et de la version de FirstClassErrors ;
+- le confinement des crashs et blocages ;
+- une frontiĂšre de timeout explicite ;
+- le signalement des Ă©checs sans nĂ©cessairement perdre toute lâexĂ©cution.
-Le worker Ă©crit son `ErrorDocumentationExtractionResult` en JSON ; le gĂ©nĂ©rateur le relit et passe Ă lâassembly suivant.
+Le worker sĂ©rialise le rĂ©sultat dâextraction en JSON, puis le gĂ©nĂ©rateur passe Ă lâassembly suivant.
-## 𧩠5. Agrégation au niveau de la solution
+Pour les rĂšgles exactes de dĂ©couverte, dâopt-in, de timeout et de politique dâĂ©chec, voir la [RĂ©fĂ©rence de lâextraction et de la dĂ©couverte des projets](DocumentationExtractionReference.fr.md).
-`SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(solutionPath[, options])` â ou `GetErrorDocumentationFromAssemblies(paths, options)` pour des binaires dĂ©jĂ compilĂ©s â travaille Ă un niveau plus Ă©levĂ© et :
+## 4. Le générateur construit un catalogue unique
-* découvre les projets (via `dotnet sln list`), ne garde que ceux qui ont opté (voir plus bas) et, sauf indication contraire, les compile
-* lance un worker pour chaque assembly de sortie
-* agrÚge tous les `ErrorDocumentation` extraits (dédupliqués par `Code`, ordonnés par `Code`)
+Au niveau de la solution, le générateur :
-Cela produit un **catalogue global des erreurs** pour lâapplication ou le systĂšme.
+1. compile la solution, sauf avec `--no-build` ;
+2. découvre les projets qui participent à la génération documentaire ;
+3. démarre un worker par assembly de sortie sélectionné ;
+4. collecte la documentation et les Ă©checs dâextraction ;
+5. déduplique et ordonne les erreurs par code.
-### Activer un projet (opt-in)
+La sortie de cette Ă©tape est un catalogue global pour lâapplication ou lâensemble dâassemblies sĂ©lectionnĂ©.
-La gĂ©nĂ©ration au niveau de la solution est **opt-in par projet** : un projet nâest documentĂ© que si son fichier projet (`.csproj`) dĂ©finit la propriĂ©tĂ© MSBuild
+La CLI expose le parcours courant :
-```xml
-
- true
-
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --format markdown \
+ --layout split \
+ --service-name my-api \
+ --output ./docs/errors
```
-Chaque projet découvert dans la solution est alors traité ainsi :
-
-| `GenerateErrorDocumentation` | Résultat |
-| ---------------------------- | -------- |
-| `true` | documenté |
-| absente | ignorĂ© â le dĂ©faut est lâopt-in |
-| `false` | toujours ignorĂ©, mĂȘme quand la politique « tout inclure » est active |
-| dĂ©clarĂ©e deux fois, ou sous `Condition` | signalĂ©, jamais devinĂ© â un avertissement qui ignore le projet (`Continue`), ou une erreur bloquante |
-
-Cela limite le catalogue â et les workers lancĂ©s pour le produire â aux projets qui dĂ©finissent rĂ©ellement des erreurs applicatives, plutĂŽt quâĂ tous les projets de la solution.
-
-La propriĂ©tĂ© est un **marqueur lu directement dans le fichier projet**, pas un interrupteur de build MSBuild : rien ne la consomme lors dâun simple `dotnet build`, et passer `-p:GenerateErrorDocumentation=âŠ` sur une ligne de commande de build nâa aucun effet. Parce quâelle est lue dans le XML du projet plutĂŽt quâĂ©valuĂ©e par MSBuild, elle doit ĂȘtre dĂ©clarĂ©e littĂ©ralement dans le `.csproj` : une valeur hĂ©ritĂ©e dâun `Directory.Build.props` partagĂ© ou apportĂ©e par un import nâest pas vue, et le projet est alors traitĂ© comme si le marqueur Ă©tait absent. Si le marqueur *est* dans le `.csproj` mais que sa valeur effective ne peut pas ĂȘtre connue depuis le seul XML â dĂ©clarĂ© plusieurs fois, ou conditionnĂ© par un `Condition` â GenDoc ne devine pas : il signale le projet via le comportement dâĂ©chec configurĂ© (un avertissement qui lâignore en mode `Continue`, une erreur bloquante sinon). Le mode `--assemblies` nâest pas soumis Ă ce filtre : il documente exactement les binaires que vous nommez.
+## 5. Les renderers transforment le catalogue en fichiers
-> Pour les appels programmatiques, `SolutionGenerationOptions` expose `OptInPropertyName` (renommer le marqueur) et `IncludeProjectsWithoutOptIn` (documenter tous les projets sans distinction). La CLI `fce` utilise les valeurs par défaut ci-dessus.
-
-## đšïž 6. Rendu vers des formats de sortie
-
-Un *renderer* transforme le catalogue en mémoire en documentation publiée. Le modÚle étant une simple donnée, le rendu est découplé derriÚre un contrat unique :
+Un renderer reçoit le catalogue structuré et un `RenderRequest`. Il renvoie un ou plusieurs `RenderedDocument`.
```csharp
public interface IErrorDocumentationRenderer {
string Format { get; }
IReadOnlyCollection SupportedLayouts { get; }
- IReadOnlyList Render(IEnumerable catalog, RenderRequest request);
+ IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request);
}
```
-Chaque renderer dĂ©clare les layouts quâil sait produire et on lui en demande un Ă chaque appel via le `RenderRequest` (qui porte aussi la culture cible) ; un layout non pris en charge est rejetĂ© par une `LayoutNotSupportedException`. Trois renderers sont fournis dâorigine :
+Les renderers intégrés fournissent actuellement :
-* **json** â un schĂ©ma JSON curĂ© et stable (layout `single` uniquement)
-* **markdown** â un fichier unique, ou (avec `--layout split`) un index README plus un fichier par groupe de source et un fichier par erreur (`single`/`split`)
-* **html** â un site statique autonome : une table des matiĂšres consultable et groupĂ©e par source et, en `split`, une page par erreur (`single`/`split`). Voir [Le renderer HTML](TheHtmlRenderer.fr.md).
+| Format | Usage | Layouts |
+| --- | --- | --- |
+| `json` | catalogue stable lisible par machine | `single` |
+| `markdown` | documentation de dépÎt ou de portail | `single`, `split` |
+| `html` | documentation statique autonome et consultable | `single`, `split` |
-Tout autre format (CSV, un gabarit maison, âŠ) est un **renderer personnalisĂ©** : implĂ©mentez lâinterface et enregistrez-le. Voir [Ăcrire son propre renderer](WritingACustomRenderer.fr.md).
+`single` produit un document unique ; `split` produit une page par erreur.
-## đ§° 7. Orchestration via CLI
+Les renderers personnalisĂ©s utilisent le mĂȘme contrat. Voir [Ăcrire son propre renderer](WritingACustomRenderer.fr.md).
-La CLI `fce` orchestre lâensemble du processus :
+## 6. La culture traverse deux frontiĂšres distinctes
-```bash
-fce generate --solution ./MyApp.sln --format markdown --layout split --output ./docs/errors
-```
+Lâinternationalisation est volontairement sĂ©parĂ©e :
-Elle gĂšre la compilation de la solution, lâextraction (via les workers), lâagrĂ©gation et le rendu. Les options courantes peuvent ĂȘtre stockĂ©es dans un fichier de configuration (`fce.json`) pour ne pas les rĂ©pĂ©ter, et les renderers personnalisĂ©s y sont aussi rĂ©fĂ©rencĂ©s :
+- la **culture dâextraction** localise le contenu des erreurs produit par les factories et mĂ©thodes de documentation ;
+- la **culture de rendu** localise les titres, libellés et autres textes fixes appartenant au renderer.
-```bash
-fce config init
-fce config renderer add ./plugins/MyCompany.Renderers.dll
-fce generate # utilise la solution, le format, lâoutput, les renderers configurĂ©sâŠ
-```
+Les identifiants stables restent indépendants de la culture : codes, identités de source, noms de clés de contexte, chemins générés, ancres et messages de diagnostic internes.
-Une valeur passée en ligne de commande écrase la configuration.
+Voir [Internationalisation](Internationalisation.fr.md) pour le workflow complet.
-## đ 8. Internationalisation
+## Pourquoi cette séparation est importante
-Le pipeline est sensible Ă la culture Ă deux niveaux : lâextracteur localise le *contenu* des erreurs (sous la culture UI demandĂ©e) et chaque renderer localise ses propres *gabarits* (depuis `RenderRequest.Culture`), tandis que les noms de fichiers et les ancres restent indĂ©pendants de la culture, pour que les liens ne cassent jamais. Câest optionnel et pilotĂ© par `fce generate --language`.
+| Composant | Responsabilité |
+| --- | --- |
+| code applicatif | sens des erreurs, rĂšgles, diagnostics, exemples, messages publics |
+| extracteur | découverte et exécution des factories documentées |
+| worker | isolation du processus et des dépendances |
+| générateur | compilation, sélection, agrégation, ordre, collecte des échecs |
+| renderer | format de fichier, layout, texte du gabarit |
+| CLI | orchestration et configuration |
-Voir **[Internationalisation](Internationalisation.fr.md)** pour le dĂ©tail â choisir la langue, le hook `DescriptionResourceType`, la localisation des gabarits de renderer, et le pilotage sans la CLI.
+Cette séparation évite plusieurs couplages :
-## đ Pourquoi cette architecture est importante
+- les factories ignorent si la sortie sera du Markdown ou du HTML ;
+- les renderers nâexĂ©cutent pas les factories applicatives ;
+- un assembly défaillant ne doit pas corrompre toutes les autres extractions ;
+- le contenu localisé et la présentation localisée restent indépendants ;
+- les appels programmatiques peuvent utiliser les étapes du pipeline sans passer par la CLI.
-Cette séparation garantit :
+## LâidĂ©e clĂ©
-| Couche | Responsabilité |
-| ----------- | --------------------------------------- |
-| Code | Définir la connaissance des erreurs |
-| Reader | Extraire la documentation structurée |
-| Worker | Exécuter le code de façon isolée |
-| Générateur | Compiler et agréger à travers les assemblies |
-| Renderer | Transformer le catalogue en un format cible |
-| CLI | Orchestrer le processus |
+> La documentation des erreurs nâest pas réécrite manuellement Ă partir du systĂšme. Elle est dĂ©rivĂ©e des mĂȘmes factories et descriptions structurĂ©es qui dĂ©finissent les Ă©checs reconnus par le systĂšme.
-La documentation reste :
-
-* proche du code
-* toujours Ă jour
-* structurée
-* exploitable par des outils
-
-## đŻ LâidĂ©e clĂ©
-
-> La documentation des erreurs nâest pas Ă©crite *Ă propos* du systĂšme.
-> Elle est dérivée *à partir* du systÚme.
-
-Le code est la source de vérité.
+Le code reste la source de vérité ; le pipeline rend cette connaissance transportable.
---
----
+---
\ No newline at end of file
diff --git a/doc/DocumentationExtractionReference.en.md b/doc/DocumentationExtractionReference.en.md
new file mode 100644
index 0000000..a66c6b9
--- /dev/null
+++ b/doc/DocumentationExtractionReference.en.md
@@ -0,0 +1,235 @@
+# Extraction and Project Discovery Reference
+
+đ **Languages:**
+đŹđ§ English (this file) | đ«đ· [Français](./DocumentationExtractionReference.fr.md)
+
+This page is the operational reference for selecting projects and assemblies, running extraction workers, and handling failures. For the mental model first, read [Architecture of the Documentation Pipeline](ArchitectureOfTheDocumentationPipeline.en.md).
+
+## Solution mode
+
+The common CLI path starts from a solution:
+
+```bash
+fce generate --solution ./MyApp.sln --format markdown --service-name my-api --output ./docs/errors
+```
+
+At a high level, solution mode:
+
+1. builds the whole solution unless `--no-build` is set;
+2. lists projects through `dotnet sln list`;
+3. selects projects according to the opt-in marker;
+4. locates their output assemblies;
+5. launches one extraction worker per assembly;
+6. aggregates documentation and failures.
+
+The build runs on the solution itself, before project selection: a compile error in a project that never opted in still fails the run.
+
+## Project opt-in
+
+A project participates when its own `.csproj` contains:
+
+```xml
+
+ true
+
+```
+
+The marker is read directly from the project XML. It is not evaluated as a normal MSBuild property.
+
+| Declaration | Result |
+| --- | --- |
+| `true` once, unconditionally | project is included |
+| absent | project is skipped |
+| `false` | project is skipped |
+| declared more than once | ambiguous and reported |
+| declared under `Condition` | ambiguous and reported |
+
+Important consequences:
+
+- declaring the value only in `Directory.Build.props` does not opt the project in;
+- importing the property from another file does not opt the project in;
+- passing `-p:GenerateErrorDocumentation=true` to `dotnet build` does not opt the project in;
+- the marker must be literal and unambiguous in the `.csproj` itself.
+
+Under a continue-on-failure policy (the default), ambiguous projects are reported and skipped. Under a strict policy (`--strict`), they fail generation.
+
+## Programmatic opt-in options
+
+`SolutionGenerationOptions` allows programmatic callers to change the defaults:
+
+- `OptInPropertyName` changes the marker name;
+- `IncludeProjectsWithoutOptIn` includes projects without the marker.
+
+The `fce` CLI uses `GenerateErrorDocumentation` and the opt-in behavior described above.
+
+## Assembly mode
+
+Use pre-built assemblies when solution discovery or building should not be part of the run:
+
+```bash
+fce generate \
+ --assemblies ./artifacts/MyApp.Domain.dll \
+ --assemblies ./artifacts/MyApp.Application.dll \
+ --format json \
+ --output ./artifacts/errors.json
+```
+
+`--assemblies` takes one path per occurrence; repeat the option for each assembly.
+
+Assembly mode documents exactly the binaries supplied. It does not apply the `.csproj` opt-in filter.
+
+Use it when:
+
+- another pipeline stage already built the application;
+- assemblies come from different solutions;
+- the caller needs exact binary selection;
+- project discovery would be inappropriate.
+
+The caller remains responsible for providing compatible dependency files and runtime assets beside the target assembly (the buildâs `.deps.json` and `runtimeconfig.json` plus the dependent assemblies).
+
+## Single-assembly extraction
+
+`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` performs in-process extraction for one already loaded assembly.
+
+It:
+
+- finds `[ProvidesErrorsFor]` classes;
+- resolves methods referenced by `[DocumentedBy]`;
+- invokes documentation methods and example factories;
+- returns an `ErrorDocumentationExtractionResult` containing documentation and failures;
+- deduplicates and orders documentation by error code.
+
+This low-level API is useful for controlled tooling and tests. Solution-level generation normally uses isolated workers instead.
+
+## Worker execution
+
+Each selected assembly is extracted in a short-lived worker process. The generator launches the worker with the target assembly's dependency context so its application dependencies and FirstClassErrors version resolve independently.
+
+The worker:
+
+1. loads the target assembly;
+2. runs extraction;
+3. serializes the complete extraction result as JSON;
+4. exits.
+
+The generator reads that result and continues with the next assembly.
+
+## Why workers are required
+
+Documentation methods and example factories are executable code. They may:
+
+- initialize static state;
+- load application dependencies;
+- use a different FirstClassErrors version;
+- throw during execution;
+- crash the process;
+- hang indefinitely.
+
+Per-assembly workers isolate those risks. A failure remains associated with the assembly that produced it.
+
+## Failures and continuation
+
+Extraction failures are data, not necessarily immediate process crashes.
+
+Failures reported by a worker that completes normally are always recorded and logged, and generation continues with the remaining assemblies regardless of the configured failure behavior:
+
+- a `[DocumentedBy]` target cannot be found;
+- the target has an invalid signature;
+- a documentation method throws;
+- an example factory throws.
+
+Process-level failures honor the configured failure behavior, which determines whether the generator records the problem and continues with other assemblies, or treats the problem as fatal:
+
+- an assembly cannot be loaded;
+- the worker exits unexpectedly;
+- the worker exceeds its timeout.
+
+A continued run can therefore produce a partial catalog plus explicit failures. Consumers must not mistake âa file was generatedâ for âevery assembly was documented successfully.â
+
+## Timeouts and process failures
+
+A worker that does not complete within its configured timeout is terminated and recorded as failed. A worker crash is also recorded with the available process information.
+
+When investigating a timeout:
+
+1. run the documented factory or example directly in a test;
+2. check for blocking I/O, deadlocks, or environment-dependent initialization;
+3. confirm the target's runtime and dependency files are available;
+4. avoid network or production-service access in documentation factories;
+5. make example factories small and deterministic.
+
+Documentation code should construct representative errors, not perform real application workflows.
+
+## Building and `--no-build`
+
+In solution mode, the generator builds the solution by default. Use `--no-build` only when the expected outputs already exist and match the current source.
+
+```bash
+fce generate --solution ./MyApp.sln --no-build --format markdown --service-name my-api --output ./docs/errors
+```
+
+A safe CI sequence is:
+
+```bash
+dotnet build MyApp.sln -c Release
+fce generate --solution MyApp.sln --configuration Release --no-build --format markdown --service-name my-api --output artifacts/errors
+```
+
+If `--no-build` points to stale or missing outputs, extraction may document old code or fail to locate assemblies.
+
+## Configuration and framework selection
+
+The selected configuration and target framework must identify a real output for each participating project. Multi-targeted projects may require an explicit framework.
+
+Keep the CLI configuration aligned with the build that produced the assemblies:
+
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --configuration Release \
+ --framework net8.0 \
+ --no-build \
+ --output ./artifacts/errors
+```
+
+## Failure-safe documentation factories
+
+A documentation method should be:
+
+- deterministic;
+- fast;
+- free of external I/O;
+- independent of environment secrets;
+- safe to execute repeatedly;
+- limited to constructing documentation and representative errors.
+
+Avoid:
+
+- database calls;
+- HTTP calls;
+- reading mutable production configuration;
+- reliance on current time or randomness when it affects output;
+- starting background work;
+- modifying global application state.
+
+## Troubleshooting checklist
+
+When expected errors are missing, verify:
+
+- the project has a literal `true` in its `.csproj`;
+- the factory class has `[ProvidesErrorsFor]`;
+- the factory has `[DocumentedBy]`;
+- the referenced method exists and has a valid documentation-factory signature;
+- the documentation method and example factories complete successfully;
+- the intended configuration and framework were built;
+- `--no-build` is not reusing stale outputs;
+- worker failures and warnings were reviewed;
+- assembly-mode paths point to the intended binaries.
+
+---
+
+
+
+---
\ No newline at end of file
diff --git a/doc/DocumentationExtractionReference.fr.md b/doc/DocumentationExtractionReference.fr.md
new file mode 100644
index 0000000..4183587
--- /dev/null
+++ b/doc/DocumentationExtractionReference.fr.md
@@ -0,0 +1,235 @@
+# RĂ©fĂ©rence de lâextraction et de la dĂ©couverte des projets
+
+đ **Langues :**
+đŹđ§ [English](./DocumentationExtractionReference.en.md) | đ«đ· Français (ce fichier)
+
+Cette page est la rĂ©fĂ©rence opĂ©rationnelle pour sĂ©lectionner les projets et assemblies, exĂ©cuter les workers dâextraction et traiter les Ă©checs. Pour commencer par le modĂšle mental, lisez [Architecture du pipeline de documentation](ArchitectureOfTheDocumentationPipeline.fr.md).
+
+## Mode solution
+
+Le parcours CLI courant part dâune solution :
+
+```bash
+fce generate --solution ./MyApp.sln --format markdown --service-name my-api --output ./docs/errors
+```
+
+Le mode solution :
+
+1. compile toute la solution, sauf avec `--no-build` ;
+2. liste les projets via `dotnet sln list` ;
+3. les sĂ©lectionne selon le marqueur dâopt-in ;
+4. localise leurs assemblies de sortie ;
+5. lance un worker dâextraction par assembly ;
+6. agrÚge la documentation et les échecs.
+
+Le build porte sur la solution elle-mĂȘme, avant la sĂ©lection des projets : une erreur de compilation dans un projet qui nâa jamais optĂ© fait quand mĂȘme Ă©chouer lâexĂ©cution.
+
+## Opt-in des projets
+
+Un projet participe lorsque son propre `.csproj` contient :
+
+```xml
+
+ true
+
+```
+
+Le marqueur est lu directement dans le XML du projet. Il nâest pas Ă©valuĂ© comme une propriĂ©tĂ© MSBuild normale.
+
+| Déclaration | Résultat |
+| --- | --- |
+| `true` une fois, sans condition | projet inclus |
+| absente | projet ignoré |
+| `false` | projet ignoré |
+| déclarée plusieurs fois | ambiguïté signalée |
+| déclarée sous `Condition` | ambiguïté signalée |
+
+Conséquences importantes :
+
+- une valeur uniquement dĂ©finie dans `Directory.Build.props` nâactive pas le projet ;
+- une propriĂ©tĂ© importĂ©e depuis un autre fichier nâactive pas le projet ;
+- `-p:GenerateErrorDocumentation=true` passĂ© Ă `dotnet build` nâactive pas le projet ;
+- le marqueur doit ĂȘtre littĂ©ral et non ambigu dans le `.csproj` lui-mĂȘme.
+
+Avec une politique de poursuite (le comportement par défaut), les projets ambigus sont signalés puis ignorés. Avec une politique stricte (`--strict`), ils font échouer la génération.
+
+## Options dâopt-in programmatiques
+
+`SolutionGenerationOptions` permet de modifier les valeurs par défaut :
+
+- `OptInPropertyName` change le nom du marqueur ;
+- `IncludeProjectsWithoutOptIn` inclut les projets sans marqueur.
+
+La CLI `fce` utilise `GenerateErrorDocumentation` et le comportement dâopt-in dĂ©crit ci-dessus.
+
+## Mode assemblies
+
+Utilisez des assemblies dĂ©jĂ compilĂ©s lorsque la dĂ©couverte de solution ou le build ne doivent pas faire partie de lâexĂ©cution :
+
+```bash
+fce generate \
+ --assemblies ./artifacts/MyApp.Domain.dll \
+ --assemblies ./artifacts/MyApp.Application.dll \
+ --format json \
+ --output ./artifacts/errors.json
+```
+
+`--assemblies` accepte un chemin par occurrence ; rĂ©pĂ©tez lâoption pour chaque assembly.
+
+Le mode assemblies documente exactement les binaires fournis. Il nâapplique pas le filtre dâopt-in du `.csproj`.
+
+Il est adapté lorsque :
+
+- une Ă©tape prĂ©cĂ©dente a dĂ©jĂ compilĂ© lâapplication ;
+- les assemblies proviennent de plusieurs solutions ;
+- lâappelant veut sĂ©lectionner exactement les binaires ;
+- la dĂ©couverte de projets nâest pas pertinente.
+
+Lâappelant reste responsable de fournir les fichiers de dĂ©pendances et assets runtime compatibles Ă cĂŽtĂ© de lâassembly cible (les `.deps.json` et `runtimeconfig.json` du build, plus les assemblies dĂ©pendants).
+
+## Extraction dâun assembly unique
+
+`AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(assembly)` effectue une extraction en processus pour un assembly déjà chargé.
+
+Cette API :
+
+- trouve les classes `[ProvidesErrorsFor]` ;
+- résout les méthodes référencées par `[DocumentedBy]` ;
+- invoque les mĂ©thodes de documentation et factories dâexemples ;
+- renvoie un `ErrorDocumentationExtractionResult` contenant documentation et échecs ;
+- dĂ©duplique et ordonne la documentation par code dâerreur.
+
+Elle est utile pour des outils contrÎlés et des tests. La génération au niveau solution utilise normalement des workers isolés.
+
+## Exécution des workers
+
+Chaque assembly sĂ©lectionnĂ© est extrait dans un processus worker Ă©phĂ©mĂšre. Le gĂ©nĂ©rateur le lance avec le contexte de dĂ©pendances de la cible afin dâisoler les dĂ©pendances applicatives et la version de FirstClassErrors.
+
+Le worker :
+
+1. charge lâassembly cible ;
+2. exĂ©cute lâextraction ;
+3. sérialise le résultat complet en JSON ;
+4. se termine.
+
+Le gĂ©nĂ©rateur lit ce rĂ©sultat puis passe Ă lâassembly suivant.
+
+## Pourquoi les workers sont nécessaires
+
+Les mĂ©thodes de documentation et factories dâexemples sont du code exĂ©cutable. Elles peuvent :
+
+- initialiser de lâĂ©tat statique ;
+- charger des dépendances applicatives ;
+- utiliser une autre version de FirstClassErrors ;
+- lever une exception ;
+- faire planter le processus ;
+- rester bloquées.
+
+Les workers par assembly isolent ces risques et rattachent lâĂ©chec Ă lâassembly qui lâa produit.
+
+## Ăchecs et poursuite
+
+Les Ă©checs dâextraction sont des donnĂ©es et ne provoquent pas nĂ©cessairement lâarrĂȘt immĂ©diat.
+
+Les Ă©checs rapportĂ©s par un worker qui se termine normalement sont toujours enregistrĂ©s et journalisĂ©s, et la gĂ©nĂ©ration poursuit avec les assemblies restants quelle que soit la politique dâĂ©chec configurĂ©e :
+
+- cible `[DocumentedBy]` introuvable ;
+- signature invalide ;
+- méthode de documentation qui lÚve ;
+- factory dâexemple qui lĂšve.
+
+Les Ă©checs de processus honorent la politique dâĂ©chec configurĂ©e, qui dĂ©cide si le gĂ©nĂ©rateur enregistre le problĂšme et poursuit avec les autres assemblies, ou le considĂšre comme fatal :
+
+- assembly impossible Ă charger ;
+- arrĂȘt inattendu du worker ;
+- timeout du worker.
+
+Une exĂ©cution poursuivie peut donc produire un catalogue partiel avec des Ă©checs explicites. La prĂ©sence dâun fichier gĂ©nĂ©rĂ© ne prouve pas que tous les assemblies ont Ă©tĂ© documentĂ©s correctement.
+
+## Timeouts et crashs
+
+Un worker qui dĂ©passe son timeout est arrĂȘtĂ© et enregistrĂ© comme Ă©chouĂ©. Un crash est Ă©galement enregistrĂ© avec les informations disponibles.
+
+Pour analyser un timeout :
+
+1. exĂ©cutez directement la factory documentĂ©e ou lâexemple dans un test ;
+2. cherchez les I/O bloquantes, deadlocks ou initialisations dĂ©pendantes de lâenvironnement ;
+3. vérifiez la présence des fichiers runtime et de dépendances ;
+4. évitez tout accÚs réseau ou service de production dans les factories de documentation ;
+5. gardez les factories dâexemples petites et dĂ©terministes.
+
+Le code de documentation doit construire des erreurs représentatives, pas exécuter de véritables workflows applicatifs.
+
+## Build et `--no-build`
+
+En mode solution, le générateur compile la solution par défaut. Utilisez `--no-build` uniquement lorsque les sorties attendues existent déjà et correspondent au code courant.
+
+```bash
+fce generate --solution ./MyApp.sln --no-build --format markdown --service-name my-api --output ./docs/errors
+```
+
+Une séquence CI sûre est :
+
+```bash
+dotnet build MyApp.sln -c Release
+fce generate --solution MyApp.sln --configuration Release --no-build --format markdown --service-name my-api --output artifacts/errors
+```
+
+Des sorties obsolÚtes ou absentes peuvent documenter un ancien code ou provoquer un échec de localisation.
+
+## Configuration et framework
+
+La configuration et le framework sélectionnés doivent identifier une sortie réelle pour chaque projet participant. Les projets multi-cibles peuvent nécessiter un framework explicite.
+
+Gardez la configuration de la CLI alignée sur le build qui a produit les assemblies :
+
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --configuration Release \
+ --framework net8.0 \
+ --no-build \
+ --output ./artifacts/errors
+```
+
+## Factories de documentation sûres
+
+Une mĂ©thode de documentation doit ĂȘtre :
+
+- déterministe ;
+- rapide ;
+- sans I/O externe ;
+- indĂ©pendante des secrets dâenvironnement ;
+- sûre à exécuter plusieurs fois ;
+- limitĂ©e Ă la construction de documentation et dâerreurs reprĂ©sentatives.
+
+Ăvitez :
+
+- les appels de base de données ;
+- les appels HTTP ;
+- la lecture de configuration de production mutable ;
+- la dĂ©pendance Ă lâheure courante ou Ă lâalĂ©atoire lorsquâelle affecte la sortie ;
+- le démarrage de tùches de fond ;
+- la modification dâĂ©tat global de lâapplication.
+
+## Checklist de dépannage
+
+Lorsque des erreurs attendues manquent, vérifiez :
+
+- la présence du marqueur littéral `true` dans le `.csproj` du projet ;
+- `[ProvidesErrorsFor]` sur la classe factory ;
+- `[DocumentedBy]` sur la factory ;
+- lâexistence et la signature de la mĂ©thode rĂ©fĂ©rencĂ©e ;
+- le succÚs des méthodes de documentation et exemples ;
+- la configuration et le framework compilés ;
+- lâabsence de sorties obsolĂštes avec `--no-build` ;
+- les avertissements et échecs des workers ;
+- les chemins exacts en mode assemblies.
+
+---
+
+
+
+---
\ No newline at end of file
diff --git a/doc/Internationalisation.fr.md b/doc/Internationalisation.fr.md
index f662249..ce32ddb 100644
--- a/doc/Internationalisation.fr.md
+++ b/doc/Internationalisation.fr.md
@@ -1,120 +1,250 @@
# Internationalisation
-đ **Langues:**
+đ **Langues :**
đŹđ§ [English](./Internationalization.en.md) | đ«đ· Français (ce fichier)
-FirstClassErrors peut produire le catalogue dâerreurs en plusieurs langues. Lâinternationalisation est **optionnelle et granulaire** : sans aucune configuration, la documentation est en anglais, et vous ne localisez que ce que vous choisissez.
+FirstClassErrors peut gĂ©nĂ©rer le mĂȘme catalogue dâerreurs dans plusieurs langues. Lâinternationalisation est optionnelle et granulaire : un projet peut tout localiser, ne localiser que certaines sources, ou ne rien localiser.
-Deux choses peuvent ĂȘtre localisĂ©es, Ă deux Ă©tapes diffĂ©rentes du pipeline :
+La rÚgle essentielle est que la localisation intervient à **deux étapes distinctes du pipeline**.
-| Quoi | Localisé quand | Comment |
-| --- | --- | --- |
-| **Contenu des erreurs** â titres, explications, rĂšgles, diagnostics, les messages publics (court et dĂ©taillĂ©), descriptions de source et de contexte | Ă lâ**extraction** | vos fabriques lisent des ressources localisĂ©es sous la culture UI courante |
-| **Gabarits des renderers** â titres, libellĂ©s, en-tĂȘtes de tableau | au **rendu** | le renderer lit son propre texte fixe pour `RenderRequest.Culture` |
+## Le modĂšle en un coup dâĆil
+
+```mermaid
+flowchart LR
+ A[Langue demandée]
+ B[Culture d'extraction]
+ C[Contenu d'erreur localisé]
+ D[Culture de rendu]
+ E[Gabarit de renderer localisé]
+ F[Catalogue généré]
+
+ A --> B --> C
+ A --> D --> E
+ C --> F
+ E --> F
+```
+
+| Ătape | Responsable de |
+| --- | --- |
+| extraction | titres, descriptions, rÚgles, hypothÚses de diagnostic, exemples, messages publics, descriptions de source et de clés de contexte |
+| rendu | titres, libellĂ©s, en-tĂȘtes, navigation et autres textes fixes du renderer |
+
+Les identités stables restent indépendantes de la culture.
-Tout le reste demeure **indĂ©pendant de la culture**, pour que les liens ne cassent jamais dâune langue Ă lâautre â et pour que les diagnostics restent dans une langue unique et cohĂ©rente pour les logs et le support : codes dâerreur, noms de source (`nameof(...)`), valeurs dâ`ErrorOrigin`, le **message de diagnostic interne** de chaque erreur, ainsi que les noms de fichiers et les ancres gĂ©nĂ©rĂ©s.
+## Ce qui reste invariant
-### Les messages publics sont localisĂ©s, le message de diagnostic ne lâest pas
+Ne localisez pas les valeurs utilisées comme contrats ou identifiants opérationnels :
-Une erreur porte trois messages, qui se localisent différemment :
+- codes dâerreur ;
+- noms de source créés avec `nameof(...)` ;
+- valeurs dâ`ErrorOrigin` ;
+- noms de clés de contexte ;
+- noms de fichiers et ancres générés ;
+- noms de champs JSON et autres schémas machine ;
+- `DiagnosticMessage`, le message runtime interne.
-* **`ShortMessage`** et **`DetailedMessage`** sont du contenu public : ils sont localisĂ©s Ă lâextraction comme nâimporte quelle autre prose â lisez-les depuis des ressources sous la culture UI courante.
-* **`DiagnosticMessage`** est dĂ©libĂ©rĂ©ment **conservĂ© dans la langue de lâauteur (indĂ©pendant de la culture)**. Il est destinĂ© aux logs, au support et aux dĂ©veloppeurs, et un texte de diagnostic est le plus utile lorsquâil se lit toujours dans une langue unique et cohĂ©rente, quelle que soit la locale de lâappelant â câest une bonne pratique assumĂ©e.
+Ces valeurs stables garantissent que les liens, branches clientes, dashboards et requĂȘtes de logs fonctionnent dans toutes les langues du catalogue.
-Ainsi, dans la documentation gĂ©nĂ©rĂ©e, les messages publics sont rendus localisĂ©s tandis que le message de diagnostic est rendu dans la langue invariante (celle de lâauteur).
+## Les trois messages runtime
-Lâexemple `.Usage` fournit cinq langues â anglais, français, espagnol, allemand et suĂ©dois (`en`, `fr`, `es`, `de`, `sv`).
+| Message | Localisé ? | Pourquoi |
+| --- | --- | --- |
+| `ShortMessage` | oui | message public pour utilisateurs ou clients dâAPI |
+| `DetailedMessage` | oui | détail public maßtrisé |
+| `DiagnosticMessage` | non | une langue interne cohérente pour les logs, le support et les développeurs |
+
+Un catalogue français peut donc afficher des messages publics français tout en conservant un message de diagnostic anglais. Câest volontaire : le diagnostic explique une occurrence runtime Ă un public interne commun.
+
+Pour les rĂšgles dâĂ©criture, voir [Ăcrire les messages dâerreur](WritingErrorMessages.fr.md).
## Choisir la langue
-Passez `--language` (alias `-l`) Ă `fce generate`, ou dĂ©finissez une valeur `language` par dĂ©faut dans `fce.json` ; une valeur en ligne de commande Ă©crase la configuration, exactement comme les autres options. Le dĂ©faut est lâanglais.
+Passez `--language` ou `-l` :
```bash
-fce generate --solution ./MyApp.sln --format markdown --language sv --output ./docs/errors
+fce generate \
+ --solution ./MyApp.sln \
+ --format markdown \
+ --language fr \
+ --service-name my-api \
+ --output ./docs/errors-fr
```
+Ou configurez une valeur par défaut dans `fce.json` :
+
```json
{
"solution": "./MyApp.sln",
- "language": "sv"
+ "language": "fr"
}
```
-## Niveau 1 â localiser le contenu des erreurs
+Une valeur en ligne de commande Ă©crase la configuration. Sans option de langue, la langue par dĂ©faut du catalogue est lâanglais.
+
+## Localiser le contenu documentaire
-Le contenu des erreurs est localisĂ© Ă lâ**extraction**. Le gĂ©nĂ©rateur lance le worker de chaque assembly avec `CultureInfo.CurrentUICulture` rĂ©glĂ© sur la langue demandĂ©e, de sorte que toute fabrique qui lit des ressources localisĂ©es produit cette langue. Dans lâexemple, la prose est lue depuis un petit wrapper de `ResourceManager` (`UsageErrorMessages`) adossĂ© Ă un `.resx` par langue :
+Pendant lâextraction, le processus worker dâextraction (voir [Architecture](ArchitectureOfTheDocumentationPipeline.fr.md)) utilise `CultureInfo.CurrentUICulture` avec la langue demandĂ©e. Les mĂ©thodes de documentation et factories peuvent donc lire normalement des ressources `.resx`.
```csharp
private static ErrorDocumentation BelowAbsoluteZeroDocumentation() {
- return DescribeError.WithTitle(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Title"))
- .WithDescription(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Description"))
- // âŠrĂšgles, diagnostics, exemples lus de la mĂȘme façon
- ;
+ return DescribeError
+ .WithTitle(Messages.Get("Temperature_BelowAbsoluteZero_Title"))
+ .WithDescription(Messages.Get("Temperature_BelowAbsoluteZero_Description"))
+ .WithRule(Messages.Get("Temperature_BelowAbsoluteZero_Rule"))
+ .WithDiagnostic(
+ Messages.Get("Temperature_BelowAbsoluteZero_Cause"),
+ ErrorOrigin.Internal,
+ Messages.Get("Temperature_BelowAbsoluteZero_Hint"))
+ .WithExamples(() => BelowAbsoluteZero(-1m));
}
```
-Vous ĂȘtes libre dâĂ©crire des chaĂźnes littĂ©rales Ă la place â lâerreur est alors simplement toujours dans cette langue (voir [Opt-in et localisation partielle](#opt-in-et-localisation-partielle)).
+La factory runtime peut localiser ses messages publics depuis la mĂȘme source :
+
+```csharp
+return DomainError.Create(
+ Code.BelowAbsoluteZero,
+ diagnosticMessage: $"Temperature {value} is below absolute zero.")
+ .WithPublicMessage(
+ shortMessage: Messages.Get("Temperature_BelowAbsoluteZero_ShortMessage"),
+ detailedMessage: Messages.Get("Temperature_BelowAbsoluteZero_DetailedMessage"));
+```
+
+Le message de diagnostic reste rédigé directement dans la langue interne choisie par le projet.
-### La description du groupe de source
+## Localiser les descriptions de source
-`[ProvidesErrorsFor]` accepte un `DescriptionResourceType`. Lorsquâil est renseignĂ©, lâextracteur traite `Description` comme une **clĂ© de ressource** rĂ©solue via ce type â le mĂȘme patron que `[Display(ResourceType = âŠ)]` de DataAnnotations. En son absence, `Description` est un texte littĂ©ral.
+`[ProvidesErrorsFor]` peut traiter `Description` comme une clé de ressource lorsque `DescriptionResourceType` est renseigné :
```csharp
-[ProvidesErrorsFor(nameof(Amount),
- Description = "Amount_Source", // une clĂ© de ressourceâŠ
- DescriptionResourceType = typeof(UsageErrorMessages))] // âŠrĂ©solue via ces ressources
+[ProvidesErrorsFor(
+ nameof(Amount),
+ Description = "Amount_Source_Description",
+ DescriptionResourceType = typeof(Messages))]
+public static class InvalidAmountError {
+}
```
-### Les descriptions de clés de contexte
+Sans `DescriptionResourceType`, la description est littĂ©rale et reste dans sa langue dâĂ©criture.
+
+## Localiser les descriptions de clés de contexte
-Une `ErrorContextKey` est enregistrĂ©e une seule fois par son nom, mais sa description peut ĂȘtre rĂ©solue paresseusement pour suivre la culture courante. Utilisez la surcharge `Func` de `Create` :
+Le nom de clĂ© reste stable, mais sa description documentaire peut ĂȘtre rĂ©solue paresseusement :
```csharp
public static readonly ErrorContextKey TransactionDate =
- ErrorContextKey.Create("TRANSACTION_DATE", () => UsageErrorMessages.Get("Bank_TransactionDate_Context"));
+ ErrorContextKey.Create(
+ "TRANSACTION_DATE",
+ () => Messages.Get("TransactionDate_Context_Description"));
+```
+
+On obtient ainsi une prose localisée dans le catalogue sans modifier la clé opérationnelle utilisée dans les logs.
+
+## Localiser les gabarits des renderers
+
+Un renderer reçoit la culture cible via `RenderRequest.Culture`. Il doit lâutiliser uniquement pour le texte qui lui appartient :
+
+```csharp
+// RendererResources est votre propre classe de ressources .resx, pas un type de la bibliothĂšque.
+string heading = RendererResources.GetString(
+ "ErrorCatalogHeading",
+ request.Culture) ?? "Error catalog";
```
-LâidentitĂ© de la clĂ© (son nom) reste figĂ©e ; seul le texte de la description est diffĂ©rĂ© et lu sous la culture en vigueur au moment de lâextraction.
+Le catalogue reçu contient dĂ©jĂ le contenu dâerreur localisĂ©. Le traduire une seconde fois mĂ©langerait les responsabilitĂ©s et pourrait produire une sortie incohĂ©rente.
-## Niveau 2 â localiser les gabarits des renderers
+Le renderer JSON garde ses noms de champs invariants : il sâagit dâun contrat machine, pas de prose utilisateur.
-Le texte fixe propre Ă un renderer (titres, libellĂ©s, en-tĂȘtes de tableau) est localisĂ© au **rendu**, depuis `RenderRequest.Culture`. Le renderer Markdown intĂ©grĂ© lit ses chaĂźnes depuis un jeu de `.resx` pour cette culture ; le renderer JSON nâa aucun texte fixe Ă traduire â ses noms de champs sont un schĂ©ma machine, pas de la prose.
+Voir [Ăcrire son propre renderer](WritingACustomRenderer.fr.md).
-Un renderer personnalisĂ© localise son gabarit de la mĂȘme façon â voir [Ăcrire son propre renderer](WritingACustomRenderer.fr.md). Le *contenu* des erreurs quâil reçoit est dĂ©jĂ localisĂ© en amont : un renderer ne localise donc jamais que son propre texte.
+## La localisation partielle est valide
-## Opt-in et localisation partielle
+Lâinternationalisation nâest pas tout ou rien.
-Lâinternationalisation nâest jamais imposĂ©e :
+Un projet peut contenir :
-* Une erreur dont le `[ProvidesErrorsFor]` nâa pas de `DescriptionResourceType` conserve sa `Description` littĂ©rale.
-* Une fabrique qui écrit des chaßnes en dur (plutÎt que de lire des ressources) reste toujours dans cette langue.
-* Sans `--language`, tout est rendu en anglais (la culture invariante), Ă lâoctet prĂšs comme avant lâexistence de lâi18n.
+- des sources entiÚrement localisées ;
+- de la documentation rédigée comme littéraux anglais ;
+- des descriptions de source issues de ressources avec des diagnostics fixes ;
+- des renderers traduits dans moins de langues que le contenu applicatif.
-Un projet ne sâinternationalise donc que lĂ oĂč il le souhaite. Lâexemple `.Usage` montre les deux extrĂȘmes : `Temperature` est un exemple simple, non localisĂ©, tandis quâ`Amount` et `BankTransactionFileValidator` sont entiĂšrement localisĂ©s dans les cinq langues.
+Lorsquâune ressource manque, le fallback appartient Ă la stratĂ©gie de ressources de lâapplication. VĂ©rifiez quâune ressource absente ne produit pas silencieusement des titres, rĂšgles ou messages publics vides.
-## Lâutiliser sans la CLI
+## Générer plusieurs langues en CI
-Lorsque vous pilotez le pipeline vous-mĂȘme, rĂ©glez la **mĂȘme** culture sur les deux Ă©tapes pour que le contenu et les gabarits concordent :
+Exécutez une génération par langue et publiez des dossiers distincts :
+
+```bash
+fce generate --solution MyApp.sln --no-build \
+ --format markdown --language en --service-name my-api \
+ --output artifacts/errors/en
+
+fce generate --solution MyApp.sln --no-build \
+ --format markdown --language fr --service-name my-api \
+ --output artifacts/errors/fr
+```
+
+Publiez ensemble les langues dâune mĂȘme version afin que le support puisse changer de langue sans ouvrir la documentation dâun autre dĂ©ploiement.
+
+Les noms de fichiers et ancres restent invariants, ce qui rend les liens inter-langues prévisibles.
+
+## Utiliser le pipeline programmatiquement
+
+Utilisez la mĂȘme culture pour lâextraction et le rendu :
```csharp
-CultureInfo culture = CultureInfo.GetCultureInfo("sv");
+CultureInfo culture = CultureInfo.GetCultureInfo("fr");
IEnumerable catalog =
SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(
- "MyApp.sln", new SolutionGenerationOptions { Culture = culture });
+ "MyApp.sln",
+ new SolutionGenerationOptions { Culture = culture });
+
+RenderRequest request = new(RenderLayouts.Single, culture, "my-api");
-RenderRequest request = new(RenderLayouts.Single, culture);
-IReadOnlyList documents = new MarkdownErrorDocumentationRenderer().Render(catalog, request);
+IReadOnlyList documents =
+ new MarkdownErrorDocumentationRenderer().Render(catalog, request);
```
-## Comment la culture traverse le pipeline
+Le nom de service est exigé par les renderers markdown et html, qui intÚgrent des exemples RFC 9457 (« Problem Details » pour les API HTTP) typés `urn:problem:{service}:{code}` ; le format json accepte `null`.
-| Ătape | Source de la culture | Ce quâelle localise |
-| --- | --- | --- |
-| Worker / extraction | `CultureInfo.CurrentUICulture` (réglée depuis `--language`) | le contenu des erreurs (titres, explications, rÚgles, diagnostics, les messages publics court et détaillé, descriptions de source et de contexte) |
-| Renderer | `RenderRequest.Culture` | le texte fixe propre au renderer (titres, libellĂ©s, en-tĂȘtes de tableau) |
+Employer volontairement deux cultures différentes produit une sortie multilingue mélangée et devrait rester exceptionnel.
+
+## Erreurs fréquentes
+
+### Localiser les codes ou noms de clés
+
+Cela casse clients, dashboards et liens. Localisez les descriptions, jamais les identités.
-Le contenu est localisĂ© Ă lâextraction ; le texte fixe au rendu. Les noms de fichiers, les ancres et le message de diagnostic interne de chaque erreur restent indĂ©pendants de la culture.
+### Localiser `DiagnosticMessage` selon lâappelant
+
+Les logs dâun mĂȘme type dâerreur deviennent dĂ©pendants de la langue et plus difficiles Ă rechercher. Gardez une langue interne unique.
+
+### Traduire le contenu applicatif dans un renderer
+
+Le renderer reçoit déjà le contenu localisé. Il possÚde uniquement son gabarit.
+
+### Croire quâun littĂ©ral est traduit automatiquement
+
+Un littéral reste littéral. Utilisez des ressources lorsque la localisation est nécessaire.
+
+### Publier des langues provenant de builds différents
+
+Les catalogues peuvent dĂ©crire des codes diffĂ©rents. GĂ©nĂ©rez toutes les langues depuis les mĂȘmes binaires et la mĂȘme version.
+
+## Checklist de revue
+
+Avant de publier des catalogues localisés, vérifiez que :
+
+- la prose publique utilise les ressources prévues ;
+- codes, clés, chemins, ancres et schémas restent invariants ;
+- `DiagnosticMessage` reste dans la langue interne choisie ;
+- les descriptions de source et de contexte suivent la culture demandée ;
+- les libellés de renderer utilisent `RenderRequest.Culture` ;
+- le fallback de ressources est explicite et testé ;
+- toutes les langues proviennent des mĂȘmes binaires ;
+- les dossiers de langue sont versionnés et publiés ensemble ;
+- les parcours CLI et programmatiques utilisent la mĂȘme culture aux deux Ă©tapes.
---
@@ -122,4 +252,4 @@ Le contenu est localisĂ© Ă lâextraction ; le texte fixe au rendu. Les noms de
â Ăcrire son propre renderer · â Table des matiĂšres · FAQ â
----
+---
\ No newline at end of file
diff --git a/doc/Internationalization.en.md b/doc/Internationalization.en.md
index b8a1e59..6c4d94c 100644
--- a/doc/Internationalization.en.md
+++ b/doc/Internationalization.en.md
@@ -3,118 +3,248 @@
đ **Languages:**
đŹđ§ English (this file) | đ«đ· [Français](./Internationalisation.fr.md)
-FirstClassErrors can produce the error catalog in several languages. Internationalization is **opt-in and granular**: with no setup the documentation is English, and you localize only what you choose.
+FirstClassErrors can generate the same error catalog in several languages. Internationalization is optional and granular: a project may localize every documented error, only selected sources, or nothing at all.
-Two things can be localized, at two different stages of the pipeline:
+The most important rule is that localization happens at **two different pipeline stages**.
-| What | Localized when | How |
-| --- | --- | --- |
-| **Error content** â titles, explanations, rules, diagnostics, the public messages (short and detailed), source and context descriptions | at **extraction** | your factories read localized resources under the current UI culture |
-| **Renderer templates** â headings, labels, table headers | at **rendering** | the renderer reads its own boilerplate for `RenderRequest.Culture` |
+## The model at a glance
+
+```mermaid
+flowchart LR
+ A[Requested language]
+ B[Extraction culture]
+ C[Localized error content]
+ D[Render culture]
+ E[Localized renderer template]
+ F[Generated catalog]
+
+ A --> B --> C
+ A --> D --> E
+ C --> F
+ E --> F
+```
+
+| Stage | Owns |
+| --- | --- |
+| extraction | titles, descriptions, rules, diagnostic hypotheses, examples, public messages, source descriptions, context-key descriptions |
+| rendering | headings, labels, table headers, navigation, and other renderer-owned boilerplate |
+
+Stable identities remain culture-invariant.
-Everything else stays **culture-invariant**, so links never break across languages â and so diagnostics stay in one consistent language for logs and support: error codes, source names (`nameof(...)`), `ErrorOrigin` values, the **internal diagnostic message** of each error, and the generated file names and anchors.
+## What remains invariant
-### Public messages are localized, the diagnostic message is not
+Do not localize values used as contracts or operational identifiers:
-An error carries three messages, and they localize differently:
+- error codes;
+- source names created with `nameof(...)`;
+- `ErrorOrigin` values;
+- context-key names;
+- generated file names and anchors;
+- JSON field names and other machine schemas;
+- `DiagnosticMessage`, the internal runtime message.
-* **`ShortMessage`** and **`DetailedMessage`** are public content, so they are localized at extraction like any other prose â read them from resources under the current UI culture.
-* **`DiagnosticMessage`** is deliberately **kept in the author language (culture-invariant)**. It is meant for logs, support and developers, and diagnostic text is most useful when it always reads in one consistent language, regardless of the caller's locale â a deliberate best practice.
+Keeping these values stable ensures that links, client branches, dashboards, and log queries work across every catalog language.
-As a result, in the generated documentation the public messages render localized while the diagnostic message renders in the invariant (author) language.
+## The three runtime messages
-The `.Usage` sample ships five languages â English, French, Spanish, German and Swedish (`en`, `fr`, `es`, `de`, `sv`).
+| Message | Localized? | Reason |
+| --- | --- | --- |
+| `ShortMessage` | yes | public message for users or API clients |
+| `DetailedMessage` | yes | controlled public detail |
+| `DiagnosticMessage` | no | one consistent internal language for logs, support, and development |
+
+A French catalog may therefore show French public messages while preserving an English diagnostic message. This is intentional: the diagnostic message identifies and explains one runtime occurrence for the internal audience.
+
+For message-writing rules, see [Writing Error Messages](WritingErrorMessages.en.md).
-## Choosing the language
+## Choose the language
-Pass `--language` (alias `-l`) to `fce generate`, or set a `language` default in `fce.json`; a command-line value overrides the configuration, exactly like the other options. The default is English.
+Pass `--language` or `-l`:
```bash
-fce generate --solution ./MyApp.sln --format markdown --language sv --output ./docs/errors
+fce generate \
+ --solution ./MyApp.sln \
+ --format markdown \
+ --language fr \
+ --service-name my-api \
+ --output ./docs/errors-fr
```
+Or configure a default in `fce.json`:
+
```json
{
"solution": "./MyApp.sln",
- "language": "sv"
+ "language": "fr"
}
```
-## Level 1 â localizing the error content
+A command-line value overrides the configuration. Without a language option, the default catalog language is English.
+
+## Localize documentation content
-Error content is localized at **extraction time**. The generator runs each assembly's worker with `CultureInfo.CurrentUICulture` set to the requested language, so any factory that reads localized resources produces that language. In the sample, the prose is read from a small `ResourceManager` wrapper (`UsageErrorMessages`) backed by a `.resx` per language:
+During extraction, the extraction worker process (see [Architecture](ArchitectureOfTheDocumentationPipeline.en.md)) runs with `CultureInfo.CurrentUICulture` set from the requested language. Documentation methods and error factories can therefore read `.resx` resources normally.
```csharp
private static ErrorDocumentation BelowAbsoluteZeroDocumentation() {
- return DescribeError.WithTitle(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Title"))
- .WithDescription(UsageErrorMessages.Get("Temperature_BelowAbsoluteZero_Description"))
- // âŠrules, diagnostics, examples read the same way
- ;
+ return DescribeError
+ .WithTitle(Messages.Get("Temperature_BelowAbsoluteZero_Title"))
+ .WithDescription(Messages.Get("Temperature_BelowAbsoluteZero_Description"))
+ .WithRule(Messages.Get("Temperature_BelowAbsoluteZero_Rule"))
+ .WithDiagnostic(
+ Messages.Get("Temperature_BelowAbsoluteZero_Cause"),
+ ErrorOrigin.Internal,
+ Messages.Get("Temperature_BelowAbsoluteZero_Hint"))
+ .WithExamples(() => BelowAbsoluteZero(-1m));
}
```
-You are free to author plain string literals instead â that error is then simply always in that one language (see [Opt-in and partial localization](#opt-in-and-partial-localization)).
+The runtime factory can localize its public messages through the same resource source:
+
+```csharp
+return DomainError.Create(
+ Code.BelowAbsoluteZero,
+ diagnosticMessage: $"Temperature {value} is below absolute zero.")
+ .WithPublicMessage(
+ shortMessage: Messages.Get("Temperature_BelowAbsoluteZero_ShortMessage"),
+ detailedMessage: Messages.Get("Temperature_BelowAbsoluteZero_DetailedMessage"));
+```
+
+The diagnostic message remains authored directly in the projectâs chosen internal language.
-### The source-group description
+## Localize source descriptions
-`[ProvidesErrorsFor]` accepts a `DescriptionResourceType`. When it is set, the extractor treats `Description` as a **resource key** resolved against that type â the same pattern as `[Display(ResourceType = âŠ)]` in DataAnnotations. When it is absent, `Description` is literal text.
+`[ProvidesErrorsFor]` can treat `Description` as a resource key when `DescriptionResourceType` is set:
```csharp
-[ProvidesErrorsFor(nameof(Amount),
- Description = "Amount_Source", // a resource keyâŠ
- DescriptionResourceType = typeof(UsageErrorMessages))] // âŠresolved against these resources
+[ProvidesErrorsFor(
+ nameof(Amount),
+ Description = "Amount_Source_Description",
+ DescriptionResourceType = typeof(Messages))]
+public static class InvalidAmountError {
+}
```
-### Context-key descriptions
+Without `DescriptionResourceType`, the description is literal and remains in its authored language.
+
+## Localize context-key descriptions
-An `ErrorContextKey` is registered once by its name, but its description can be resolved lazily so it follows the current culture. Use the `Func` overload of `Create`:
+The key name remains stable, but its documentation description can be resolved lazily:
```csharp
public static readonly ErrorContextKey TransactionDate =
- ErrorContextKey.Create("TRANSACTION_DATE", () => UsageErrorMessages.Get("Bank_TransactionDate_Context"));
+ ErrorContextKey.Create(
+ "TRANSACTION_DATE",
+ () => Messages.Get("TransactionDate_Context_Description"));
+```
+
+This produces localized catalog prose without changing the operational key used in logs.
+
+## Localize renderer templates
+
+A renderer receives the target culture through `RenderRequest.Culture`. It should use that culture only for text that belongs to the renderer:
+
+```csharp
+// RendererResources is your own .resx-backed resource class, not a library type.
+string heading = RendererResources.GetString(
+ "ErrorCatalogHeading",
+ request.Culture) ?? "Error catalog";
```
-The key's identity (its name) stays fixed; only the description text is deferred and read under the culture in effect when the documentation is extracted.
+The catalog passed to the renderer already contains localized error content. Translating it again would mix responsibilities and can produce inconsistent output.
-## Level 2 â localizing the renderer templates
+The JSON renderer keeps its schema field names invariant because they are a machine contract, not user-facing prose.
-A renderer's own boilerplate (headings, labels, table headers) is localized at **rendering time**, from `RenderRequest.Culture`. The built-in Markdown renderer reads its strings from a `.resx` set for that culture; the JSON renderer has no boilerplate to translate â its field names are a machine schema, not prose.
+See [Writing a custom renderer](WritingACustomRenderer.en.md).
-A custom renderer localizes its template the same way â see [Writing a custom renderer](WritingACustomRenderer.en.md). The error *content* it receives is already localized upstream, so a renderer only ever localizes its own text.
+## Partial localization is valid
-## Opt-in and partial localization
+Internationalization is not all-or-nothing.
-Internationalization is never forced:
+A project may contain:
-* An error whose `[ProvidesErrorsFor]` has no `DescriptionResourceType` keeps its literal `Description`.
-* A factory that authors plain strings (rather than reading resources) is always in that one language.
-* Without `--language`, everything renders in English (the invariant culture), byte-for-byte as before i18n existed.
+- fully localized sources;
+- documentation written as English literals;
+- source descriptions backed by resources but fixed diagnostic messages;
+- custom renderers localized in fewer languages than the application content.
-So a project internationalizes only where it wants to. The `.Usage` sample shows both ends: `Temperature` is a plain, non-localized example, while `Amount` and `BankTransactionFileValidator` are fully localized across the five languages.
+When a resource is unavailable, the fallback behavior belongs to the applicationâs resource strategy. Ensure that missing resources do not silently produce empty titles, rules, or public messages.
-## Using it without the CLI
+## Generate several languages in CI
-When you drive the pipeline yourself, set the **same** culture on both stages so the content and the templates match:
+Run one generation per language and publish separate directories:
+
+```bash
+fce generate --solution MyApp.sln --no-build \
+ --format markdown --language en --service-name my-api \
+ --output artifacts/errors/en
+
+fce generate --solution MyApp.sln --no-build \
+ --format markdown --language fr --service-name my-api \
+ --output artifacts/errors/fr
+```
+
+Keep identical catalog versions together so support can switch language without opening documentation from another deployment.
+
+File names and anchors remain invariant, which makes language switching and cross-language links predictable.
+
+## Use the pipeline programmatically
+
+Set the same culture for extraction and rendering:
```csharp
-CultureInfo culture = CultureInfo.GetCultureInfo("sv");
+CultureInfo culture = CultureInfo.GetCultureInfo("fr");
IEnumerable catalog =
SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(
- "MyApp.sln", new SolutionGenerationOptions { Culture = culture });
+ "MyApp.sln",
+ new SolutionGenerationOptions { Culture = culture });
+
+RenderRequest request = new(RenderLayouts.Single, culture, "my-api");
-RenderRequest request = new(RenderLayouts.Single, culture);
-IReadOnlyList documents = new MarkdownErrorDocumentationRenderer().Render(catalog, request);
+IReadOnlyList documents =
+ new MarkdownErrorDocumentationRenderer().Render(catalog, request);
```
-## How the culture flows through the pipeline
+The service name is required by the markdown and html renderers, which embed RFC 9457 (Problem Details for HTTP APIs) examples typed `urn:problem:{service}:{code}`; the json format accepts `null`.
-| Stage | Culture source | What it localizes |
-| --- | --- | --- |
-| Worker / extraction | `CultureInfo.CurrentUICulture` (set from `--language`) | error content (titles, explanations, rules, diagnostics, the public short and detailed messages, source and context descriptions) |
-| Renderer | `RenderRequest.Culture` | the renderer's own templates (headings, labels, table headers) |
+Using different cultures intentionally produces mixed-language output and should be rare.
+
+## Common mistakes
+
+### Localizing codes or key names
+
+This breaks clients, dashboards, and links. Localize descriptions, never identities.
-Content is localized at extraction; boilerplate at rendering. File names, anchors, and each error's internal diagnostic message stay culture-invariant.
+### Localizing `DiagnosticMessage` per caller
+
+Logs for the same error type become language-dependent and harder to search. Keep one internal author language.
+
+### Translating application content inside a renderer
+
+The renderer receives already localized content. It owns only its template.
+
+### Treating literal text as automatically translated
+
+A literal remains literal. Use resource-backed values where localization is required.
+
+### Publishing languages from different builds
+
+The catalogs may describe different code. Generate every language from the same build and version.
+
+## Review checklist
+
+Before publishing localized catalogs, verify that:
+
+- public prose is backed by the intended resources;
+- codes, keys, paths, anchors, and schemas stay invariant;
+- `DiagnosticMessage` stays in the chosen internal language;
+- source and context descriptions resolve in the requested culture;
+- renderer labels use `RenderRequest.Culture`;
+- missing-resource fallback is explicit and tested;
+- every language is generated from the same binaries;
+- language directories are versioned and published together;
+- the CLI and programmatic paths use the same culture for extraction and rendering.
---
@@ -122,4 +252,4 @@ Content is localized at extraction; boilerplate at rendering. File names, anchor
â Writing a custom renderer · â Table of contents · FAQ â
----
+---
\ No newline at end of file
diff --git a/doc/WritingACustomRenderer.en.md b/doc/WritingACustomRenderer.en.md
index b54ff6e..c83d901 100644
--- a/doc/WritingACustomRenderer.en.md
+++ b/doc/WritingACustomRenderer.en.md
@@ -3,37 +3,100 @@
đ **Languages:**
đŹđ§ English (this file) | đ«đ· [Français](./WritingACustomRenderer.fr.md)
-The built-in `json` and `markdown` formats cover the common cases, but any output format â HTML, CSV, a company documentation template â can be added as a **custom renderer**. A renderer depends only on the documentation model, not on how the catalog was produced, so writing one is small and self-contained.
+A custom renderer turns the extracted `ErrorDocumentation` catalog into a format owned by your organization: CSV, a documentation portal payload, a company-specific static site, or another machine-readable representation.
-## The contract
+A renderer does **not** discover projects or execute error factories. It receives an already extracted catalog and decides only how to present it.
+
+## The workflow
+
+```mermaid
+flowchart LR
+ A[Extracted ErrorDocumentation catalog]
+ B[Custom renderer]
+ C[One or more RenderedDocument values]
+ D[Files written by CLI or application]
+
+ A --> B --> C --> D
+```
+
+To add a format:
+
+1. reference `FirstClassErrors`;
+2. implement `IErrorDocumentationRenderer`;
+3. declare a unique `Format` and supported layouts;
+4. return one or more `RenderedDocument` values;
+5. package the renderer in a loadable assembly;
+6. register that assembly in `fce.json` or with the CLI.
-A renderer implements `IErrorDocumentationRenderer` (shipped in the `FirstClassErrors` package, namespace `FirstClassErrors.GenDoc.Rendering`):
+## The contract
```csharp
public interface IErrorDocumentationRenderer {
- // The value selected with `fce generate --format <âŠ>`.
string Format { get; }
-
- // The layouts this renderer can produce, e.g. "single", "split" (see RenderLayouts).
IReadOnlyCollection SupportedLayouts { get; }
+ IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request);
+}
+```
+
+The renderer contract types live in the `FirstClassErrors.GenDoc.Rendering` namespace of the `FirstClassErrors` package; the documentation model itself (`ErrorDocumentation` and related types) lives in the `FirstClassErrors` namespace.
+
+### `Format`
+
+`Format` is the value selected by `--format`:
- // Turn the catalog into one or more output files for the requested layout and culture.
- IReadOnlyList Render(IEnumerable catalog, RenderRequest request);
+```csharp
+public string Format => "csv";
+```
+
+Choose a stable, lowercase name. Built-in formats take precedence if a custom renderer reuses `json`, `markdown`, or `html`.
+
+### `SupportedLayouts`
+
+A layout describes the shape of the output, not its format:
+
+```csharp
+public IReadOnlyCollection SupportedLayouts { get; } =
+ new[] { RenderLayouts.Single };
+```
+
+The built-in names are `single` and `split`, but a custom renderer may define another string when its output model requires it.
+
+Reject an unsupported request explicitly:
+
+```csharp
+if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) {
+ throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts);
}
```
-`RenderedDocument` is a `(RelativePath, Content)` pair. Return a single document for a one-file format, or several (an index plus one file per error) for a multi-file one â the `RelativePath` is used as the file name when the output target is a directory.
+### `RenderedDocument`
+
+Each returned document contains:
+
+- `RelativePath`, the path below the selected output location;
+- `Content`, the complete file contents.
-`RenderRequest` carries the two per-call choices:
+Return one document for a single-file format. Return several documents for a site or split layout.
-* **`Layout`** â the value of `fce generate --layout <âŠ>`. Declare the layouts you support in `SupportedLayouts` and reject any other with `LayoutNotSupportedException` (the built-in `json` renderer supports only `single`; `markdown` supports `single` and `split`). A layout is a free-form string, so a renderer may define its own.
-* **`Culture`** â the target language. Localize any boilerplate you emit for `request.Culture` (the error *content* is already localized upstream by the extractor, so a renderer only localizes its own template text). See [Internationalization](Internationalization.en.md).
+Keep paths relative, deterministic, and safe. Do not write files directly inside `Render(...)`; the caller owns the output destination.
-The contract and the model (`ErrorDocumentation`, `ErrorDiagnostic`, âŠ) ship in the `FirstClassErrors` package, which targets **.NET Standard 2.0** â so a renderer needs only that one reference, which most projects already have.
+### `RenderRequest`
-## A minimal example
+`RenderRequest` carries:
+
+- `Layout`, selected by `--layout`;
+- `Culture`, used for renderer-owned headings, labels, and template text;
+- `ServiceName`, set from `--service-name` or the configuration, used by renderers that emit RFC 9457 (Problem Details for HTTP APIs) problem types (`urn:problem:{service}:{code}`); `null` when none is configured.
+
+The catalog content has already been localized during extraction. A renderer must not reinterpret or retranslate error titles, rules, messages, or diagnostics.
+
+## Complete minimal CSV renderer
```csharp
+using System;
+using System.Collections.Generic;
using System.Linq;
using FirstClassErrors;
@@ -43,36 +106,46 @@ public sealed class CsvErrorDocumentationRenderer : IErrorDocumentationRenderer
public string Format => "csv";
- // A single CSV file â this renderer supports only the "single" layout.
- public IReadOnlyCollection SupportedLayouts { get; } = new[] { RenderLayouts.Single };
+ public IReadOnlyCollection SupportedLayouts { get; } =
+ new[] { RenderLayouts.Single };
+
+ public IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request) {
- public IReadOnlyList Render(IEnumerable catalog, RenderRequest request) {
if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) {
throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts);
}
- var rows = catalog.Select(error => $"{error.Code},{Quote(error.Title)}");
- var content = "code,title\n" + string.Join("\n", rows);
+ IEnumerable rows = catalog.Select(error =>
+ $"{Quote(error.Code.ToString())},{Quote(error.Title)}");
- return new[] { new RenderedDocument("errors.csv", content) };
+ string content = "code,title\n" + string.Join("\n", rows);
+
+ return new[] {
+ new RenderedDocument("errors.csv", content)
+ };
}
- private static string Quote(string? value) => $"\"{(value ?? string.Empty).Replace("\"", "\"\"")}\"";
+ private static string Quote(string? value) {
+ string escaped = (value ?? string.Empty).Replace("\"", "\"\"");
+ return $"\"{escaped}\"";
+ }
}
```
-That is a complete renderer. (This CSV has no boilerplate to translate; a renderer that emits headings or labels would read them from resources keyed by `request.Culture`.)
+This renderer is deterministic, supports one layout, and returns one complete file.
-## Plugging it into the CLI
+## Register it with the CLI
-Build your renderer into a library, then register it:
+Build the renderer into a class library, then register the assembly:
```bash
fce config renderer add ./plugins/MyCompany.Renderers.dll
-fce generate --solution MyApp.sln --format csv --output errors.csv
+fce config renderer list
```
-`fce config renderer add` records the library path in `fce.json` (you can also edit the file by hand). At generation time the CLI loads the referenced assemblies, discovers every public renderer with a parameterless constructor, and selects the one whose `Format` matches `--format`. `fce config renderer list` shows the built-in and configured formats, and an unknown `--format` lists what is available.
+The configuration contains the registered path:
```json
{
@@ -80,38 +153,94 @@ fce generate --solution MyApp.sln --format csv --output errors.csv
}
```
-Paths are absolute or relative to `fce.json`, so a configuration is portable with its plugins.
+Paths may be absolute or relative to `fce.json`. Relative paths make a repository-local configuration portable.
+
+Generate with the new format:
+
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --format csv \
+ --layout single \
+ --output ./artifacts/errors.csv
+```
+
+## Discovery and loading rules
-### Things to know
+The CLI discovers public renderer types in configured assemblies. A renderer must:
-* **Parameterless constructor** â the CLI instantiates renderers by reflection.
-* **Shared contract** â reference `FirstClassErrors`, but do not ship your own copy of it next to the CLI: the renderer type must resolve to the CLI's contract assembly. Reference it without copying (e.g. `false` on the reference), or rely on the identical version already sitting beside the CLI.
-* **Target framework** â the CLI loads the plugin into its own process, so build it for a framework the CLI can load.
-* **Built-ins win ties** â if a custom renderer declares `json` or `markdown`, the built-in one is used.
-* **Failures are tolerated** â a plugin that cannot be loaded is reported as a warning and skipped; it does not abort generation.
+- implement `IErrorDocumentationRenderer`;
+- be public;
+- have a parameterless constructor;
+- use the contract assembly resolved by the CLI process;
+- target a framework loadable by that CLI runtime.
-## Using a renderer without the CLI
+Do not deploy a private copy of `FirstClassErrors` beside the plugin: if the CLI and the plugin each load their own copy, `IErrorDocumentationRenderer` becomes two distinct types and the renderer is silently not recognized. A project reference may use `false` when appropriate for the packaging layout.
-The CLI is optional â a renderer is just a class. If you obtain a catalog yourself (for instance via `SolutionErrorDocumentationGenerator`, in `FirstClassErrors.GenDoc`), rendering it is:
+A plugin that cannot be loaded is reported and skipped. Review those warnings: an unknown format later in the command may simply mean its plugin failed to load.
+
+## Localize renderer-owned text
+
+A CSV schema may have no translated template text. A renderer producing headings should resolve only those headings from `request.Culture`:
+
+```csharp
+// RendererResources is your own .resx-backed resource class, not a library type.
+string heading = RendererResources.GetString("ErrorCatalog", request.Culture)
+ ?? "Error catalog";
+```
+
+Keep these responsibilities separate:
+
+| Content | Owner |
+| --- | --- |
+| error title, description, rule, diagnostics, public messages | extraction and application resources |
+| headings, labels, navigation text, table headers | renderer resources |
+| codes, paths, anchors, schema field names | culture-invariant contract |
+
+See [Internationalization](Internationalization.en.md).
+
+## Use it without the CLI
+
+A renderer is an ordinary class. Programmatic callers can extract and render directly:
```csharp
-// Use one culture for both levels: it localizes the extracted content and the rendered boilerplate.
CultureInfo culture = CultureInfo.GetCultureInfo("en");
IEnumerable catalog =
SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(
- "MyApp.sln", new SolutionGenerationOptions { Culture = culture });
+ "MyApp.sln",
+ new SolutionGenerationOptions { Culture = culture });
RenderRequest request = new(RenderLayouts.Single, culture);
-foreach (RenderedDocument document in new CsvErrorDocumentationRenderer().Render(catalog, request)) {
+
+foreach (RenderedDocument document in
+ new CsvErrorDocumentationRenderer().Render(catalog, request)) {
File.WriteAllText(document.RelativePath, document.Content);
}
```
+The same culture should be used for extraction and rendering unless mixed-language output is deliberate.
+
+## Renderer design checklist
+
+Before publishing a renderer, verify that:
+
+- `Format` is stable and does not collide with a built-in format;
+- every declared layout is implemented;
+- unsupported layouts throw `LayoutNotSupportedException`;
+- output paths are relative and deterministic;
+- output order is deterministic;
+- machine schemas do not change accidentally;
+- escaping is correct for the target format;
+- renderer-owned text uses `request.Culture`;
+- application-owned content is not translated a second time;
+- the renderer performs no external I/O inside `Render(...)`;
+- the plugin loads without warnings in the CLI environment.
+
---
----
+---
\ No newline at end of file
diff --git a/doc/WritingACustomRenderer.fr.md b/doc/WritingACustomRenderer.fr.md
index d1e8397..18bcbcd 100644
--- a/doc/WritingACustomRenderer.fr.md
+++ b/doc/WritingACustomRenderer.fr.md
@@ -1,39 +1,102 @@
# Ăcrire son propre renderer
-đ **Langues:**
+đ **Langues :**
đŹđ§ [English](./WritingACustomRenderer.en.md) | đ«đ· Français (ce fichier)
-Les formats intĂ©grĂ©s `json` et `markdown` couvrent les cas courants, mais nâimporte quel format de sortie â HTML, CSV, un gabarit de documentation maison â peut ĂȘtre ajoutĂ© sous forme de **renderer personnalisĂ©**. Un renderer ne dĂ©pend que du modĂšle de documentation, pas de la façon dont le catalogue a Ă©tĂ© produit : en Ă©crire un est donc court et autonome.
+Un renderer personnalisé transforme le catalogue `ErrorDocumentation` déjà extrait dans un format appartenant à votre organisation : CSV, payload pour un portail documentaire, site statique maison ou autre représentation lisible par machine.
-## Le contrat
+Un renderer ne dĂ©couvre pas les projets et nâexĂ©cute pas les factories dâerreur. Il reçoit un catalogue dĂ©jĂ extrait et dĂ©cide uniquement de sa prĂ©sentation.
+
+## Le workflow
+
+```mermaid
+flowchart LR
+ A[Catalogue ErrorDocumentation extrait]
+ B[Renderer personnalisé]
+ C[Un ou plusieurs RenderedDocument]
+ D[Fichiers écrits par la CLI ou l'application]
+
+ A --> B --> C --> D
+```
+
+Pour ajouter un format :
+
+1. référencez `FirstClassErrors` ;
+2. implémentez `IErrorDocumentationRenderer` ;
+3. déclarez un `Format` unique et les layouts pris en charge ;
+4. renvoyez un ou plusieurs `RenderedDocument` ;
+5. packagez le renderer dans un assembly chargeable ;
+6. enregistrez cet assembly dans `fce.json` ou avec la CLI.
-Un renderer implémente `IErrorDocumentationRenderer` (fourni dans le package `FirstClassErrors`, namespace `FirstClassErrors.GenDoc.Rendering`) :
+## Le contrat
```csharp
public interface IErrorDocumentationRenderer {
- // La valeur choisie avec `fce generate --format <âŠ>`.
string Format { get; }
-
- // Les layouts que ce renderer sait produire, ex. "single", "split" (voir RenderLayouts).
IReadOnlyCollection SupportedLayouts { get; }
+ IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request);
+}
+```
+
+Les types du contrat de rendu sont dans le namespace `FirstClassErrors.GenDoc.Rendering` du package `FirstClassErrors` ; le modĂšle documentaire lui-mĂȘme (`ErrorDocumentation` et les types associĂ©s) est dans le namespace `FirstClassErrors`.
+
+### `Format`
+
+`Format` est la valeur sélectionnée par `--format` :
- // Transforme le catalogue en un ou plusieurs fichiers de sortie pour le layout et la culture demandés.
- IReadOnlyList Render(IEnumerable catalog, RenderRequest request);
+```csharp
+public string Format => "csv";
+```
+
+Choisissez un nom stable en minuscules. Les formats intégrés restent prioritaires si un renderer personnalisé réutilise `json`, `markdown` ou `html`.
+
+### `SupportedLayouts`
+
+Un layout décrit la forme de la sortie, pas son format :
+
+```csharp
+public IReadOnlyCollection SupportedLayouts { get; } =
+ new[] { RenderLayouts.Single };
+```
+
+Les noms intĂ©grĂ©s sont `single` et `split`, mais un renderer peut dĂ©finir une autre chaĂźne si son modĂšle de sortie lâexige.
+
+Rejetez explicitement un layout non supporté :
+
+```csharp
+if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) {
+ throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts);
}
```
-`RenderedDocument` est un couple `(RelativePath, Content)`. Renvoyez un seul document pour un format mono-fichier, ou plusieurs (un index plus un fichier par erreur) pour un format multi-fichiers â le `RelativePath` sert de nom de fichier lorsque la sortie est un dossier.
+### `RenderedDocument`
+
+Chaque document renvoyé contient :
+
+- `RelativePath`, le chemin sous lâemplacement de sortie choisi ;
+- `Content`, le contenu complet du fichier.
-`RenderRequest` porte les deux choix propres Ă chaque appel :
+Renvoyez un document pour un format mono-fichier et plusieurs documents pour un site ou un layout splitté.
-* **`Layout`** â la valeur de `fce generate --layout <âŠ>`. DĂ©clarez les layouts pris en charge dans `SupportedLayouts` et rejetez tout autre avec `LayoutNotSupportedException` (le renderer intĂ©grĂ© `json` ne gĂšre que `single` ; `markdown` gĂšre `single` et `split`). Un layout est une simple chaĂźne : un renderer peut donc dĂ©finir les siens.
-* **`Culture`** â la langue cible. Localisez le texte fixe que vous produisez pour `request.Culture` (le *contenu* des erreurs est dĂ©jĂ localisĂ© en amont par lâextracteur : un renderer ne localise donc que son propre gabarit). Voir [Internationalisation](Internationalisation.fr.md).
+Gardez les chemins relatifs, dĂ©terministes et sĂ»rs. NâĂ©crivez pas directement les fichiers dans `Render(...)` : lâappelant possĂšde la destination.
-Le contrat et le modĂšle (`ErrorDocumentation`, `ErrorDiagnostic`, âŠ) sont livrĂ©s dans le package `FirstClassErrors`, qui cible **.NET Standard 2.0** â un renderer nâa donc besoin que de cette seule rĂ©fĂ©rence, que la plupart des projets possĂšdent dĂ©jĂ .
+### `RenderRequest`
-## Un exemple minimal
+`RenderRequest` porte :
+
+- `Layout`, sélectionné avec `--layout` ;
+- `Culture`, utilisée pour les titres, libellés et textes fixes appartenant au renderer ;
+- `ServiceName`, issu de `--service-name` ou de la configuration, utilisé par les renderers qui émettent des types de problÚme RFC 9457 (« Problem Details » pour les API HTTP) de la forme `urn:problem:{service}:{code}` ; `null` si non configuré.
+
+Le contenu du catalogue a dĂ©jĂ Ă©tĂ© localisĂ© pendant lâextraction. Un renderer ne doit pas retraduire les titres, rĂšgles, messages ou diagnostics des erreurs.
+
+## Renderer CSV minimal complet
```csharp
+using System;
+using System.Collections.Generic;
using System.Linq;
using FirstClassErrors;
@@ -43,36 +106,46 @@ public sealed class CsvErrorDocumentationRenderer : IErrorDocumentationRenderer
public string Format => "csv";
- // Un seul fichier CSV â ce renderer ne gĂšre que le layout « single ».
- public IReadOnlyCollection SupportedLayouts { get; } = new[] { RenderLayouts.Single };
+ public IReadOnlyCollection SupportedLayouts { get; } =
+ new[] { RenderLayouts.Single };
+
+ public IReadOnlyList Render(
+ IEnumerable catalog,
+ RenderRequest request) {
- public IReadOnlyList Render(IEnumerable catalog, RenderRequest request) {
if (!SupportedLayouts.Contains(request.Layout, StringComparer.OrdinalIgnoreCase)) {
throw new LayoutNotSupportedException(Format, request.Layout, SupportedLayouts);
}
- var rows = catalog.Select(error => $"{error.Code},{Quote(error.Title)}");
- var content = "code,title\n" + string.Join("\n", rows);
+ IEnumerable rows = catalog.Select(error =>
+ $"{Quote(error.Code.ToString())},{Quote(error.Title)}");
- return new[] { new RenderedDocument("errors.csv", content) };
+ string content = "code,title\n" + string.Join("\n", rows);
+
+ return new[] {
+ new RenderedDocument("errors.csv", content)
+ };
}
- private static string Quote(string? value) => $"\"{(value ?? string.Empty).Replace("\"", "\"\"")}\"";
+ private static string Quote(string? value) {
+ string escaped = (value ?? string.Empty).Replace("\"", "\"\"");
+ return $"\"{escaped}\"";
+ }
}
```
-Câest un renderer complet. (Ce CSV nâa aucun texte fixe Ă traduire ; un renderer qui Ă©met des titres ou des libellĂ©s les lirait depuis des ressources indexĂ©es par `request.Culture`.)
+Ce renderer est déterministe, prend en charge un seul layout et renvoie un fichier complet.
-## Le brancher sur la CLI
+## Lâenregistrer dans la CLI
-Compilez votre renderer dans une bibliothĂšque, puis enregistrez-le :
+Compilez le renderer dans une bibliothĂšque, puis enregistrez lâassembly :
```bash
fce config renderer add ./plugins/MyCompany.Renderers.dll
-fce generate --solution MyApp.sln --format csv --output errors.csv
+fce config renderer list
```
-`fce config renderer add` inscrit le chemin de la bibliothĂšque dans `fce.json` (vous pouvez aussi Ă©diter le fichier Ă la main). Au moment de la gĂ©nĂ©ration, la CLI charge les assemblies rĂ©fĂ©rencĂ©es, y dĂ©couvre chaque renderer public dotĂ© dâun constructeur sans paramĂštre, et sĂ©lectionne celui dont le `Format` correspond Ă `--format`. `fce config renderer list` affiche les formats intĂ©grĂ©s et configurĂ©s, et un `--format` inconnu liste ce qui est disponible.
+La configuration contient le chemin enregistré :
```json
{
@@ -80,38 +153,94 @@ fce generate --solution MyApp.sln --format csv --output errors.csv
}
```
-Les chemins sont absolus ou relatifs Ă `fce.json`, de sorte quâune configuration est portable avec ses plugins.
+Les chemins peuvent ĂȘtre absolus ou relatifs Ă `fce.json`. Les chemins relatifs rendent la configuration du dĂ©pĂŽt portable.
+
+Générez avec le nouveau format :
+
+```bash
+fce generate \
+ --solution ./MyApp.sln \
+ --format csv \
+ --layout single \
+ --output ./artifacts/errors.csv
+```
+
+## RÚgles de découverte et de chargement
-### Ă savoir
+La CLI découvre les types de renderer publics dans les assemblies configurés. Un renderer doit :
-* **Constructeur sans paramĂštre** â la CLI instancie les renderers par rĂ©flexion.
-* **Contrat partagĂ©** â rĂ©fĂ©rencez `FirstClassErrors`, mais ne livrez pas votre propre copie de cet assembly Ă cĂŽtĂ© de la CLI : le type du renderer doit se rĂ©soudre vers lâassembly de contrat de la CLI. RĂ©fĂ©rencez-le sans le copier (par ex. `false` sur la rĂ©fĂ©rence), ou appuyez-vous sur la version identique dĂ©jĂ prĂ©sente Ă cĂŽtĂ© de la CLI.
-* **Framework cible** â la CLI charge le plugin dans son propre processus ; compilez-le pour un framework quâelle peut charger.
-* **Les intĂ©grĂ©s gagnent les Ă©galitĂ©s** â si un renderer personnalisĂ© dĂ©clare `json` ou `markdown`, câest lâintĂ©grĂ© qui est utilisĂ©.
-* **Les Ă©checs sont tolĂ©rĂ©s** â un plugin impossible Ă charger est signalĂ© par un avertissement et ignorĂ© ; il nâinterrompt pas la gĂ©nĂ©ration.
+- implémenter `IErrorDocumentationRenderer` ;
+- ĂȘtre public ;
+- posséder un constructeur sans paramÚtre ;
+- utiliser lâassembly de contrat rĂ©solu par le processus CLI ;
+- cibler un framework chargeable par ce runtime.
-## Utiliser un renderer sans la CLI
+Ne dĂ©ployez pas de copie privĂ©e de `FirstClassErrors` Ă cĂŽtĂ© du plugin : si la CLI et le plugin chargent chacun leur propre copie, `IErrorDocumentationRenderer` devient deux types distincts et le renderer nâest silencieusement pas reconnu. Une rĂ©fĂ©rence projet peut utiliser `false` lorsque ce choix correspond au packaging.
-La CLI est optionnelle â un renderer nâest quâune classe. Si vous obtenez un catalogue vous-mĂȘme (par exemple via `SolutionErrorDocumentationGenerator`, dans `FirstClassErrors.GenDoc`), le rendre se rĂ©sume Ă :
+Un plugin impossible Ă charger est signalĂ© puis ignorĂ©. Examinez ces avertissements : un format inconnu peut simplement indiquer que son plugin nâa pas Ă©tĂ© chargĂ©.
+
+## Localiser le texte du renderer
+
+Un schéma CSV peut ne contenir aucun texte à traduire. Un renderer avec des titres doit résoudre uniquement ses propres libellés depuis `request.Culture` :
+
+```csharp
+// RendererResources est votre propre classe de ressources .resx, pas un type de la bibliothĂšque.
+string heading = RendererResources.GetString("ErrorCatalog", request.Culture)
+ ?? "Error catalog";
+```
+
+Gardez ces responsabilités séparées :
+
+| Contenu | Responsable |
+| --- | --- |
+| titre, description, rĂšgle, diagnostics, messages publics | extraction et ressources applicatives |
+| titres, libellĂ©s, navigation, en-tĂȘtes | ressources du renderer |
+| codes, chemins, ancres, champs de schéma | contrat indépendant de la culture |
+
+Voir [Internationalisation](Internationalisation.fr.md).
+
+## Lâutiliser sans la CLI
+
+Un renderer est une classe ordinaire. Un appelant programmatique peut extraire puis rendre directement :
```csharp
-// Une seule culture pour les deux niveaux : elle localise le contenu extrait et le texte fixe rendu.
CultureInfo culture = CultureInfo.GetCultureInfo("fr");
IEnumerable catalog =
SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(
- "MyApp.sln", new SolutionGenerationOptions { Culture = culture });
+ "MyApp.sln",
+ new SolutionGenerationOptions { Culture = culture });
RenderRequest request = new(RenderLayouts.Single, culture);
-foreach (RenderedDocument document in new CsvErrorDocumentationRenderer().Render(catalog, request)) {
+
+foreach (RenderedDocument document in
+ new CsvErrorDocumentationRenderer().Render(catalog, request)) {
File.WriteAllText(document.RelativePath, document.Content);
}
```
+Utilisez la mĂȘme culture pour lâextraction et le rendu, sauf si une sortie multilingue mĂ©langĂ©e est volontaire.
+
+## Checklist de conception
+
+Avant de publier un renderer, vérifiez que :
+
+- `Format` est stable et ne collisionne pas avec un format intégré ;
+- chaque layout déclaré est implémenté ;
+- les layouts non supportés lÚvent `LayoutNotSupportedException` ;
+- les chemins de sortie sont relatifs et déterministes ;
+- lâordre de sortie est dĂ©terministe ;
+- les schémas machine ne changent pas accidentellement ;
+- lâĂ©chappement correspond au format cible ;
+- les textes du renderer utilisent `request.Culture` ;
+- le contenu applicatif nâest pas traduit une seconde fois ;
+- `Render(...)` nâeffectue pas dâI/O externe ;
+- le plugin se charge sans avertissement dans lâenvironnement CLI.
+
---
----
+---
\ No newline at end of file