Sign in Join
Wiki copytemporary

Translations / i18n / localizations for programs/fr: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Vue d'ensemble
22
33Cette page concerne la façon dont un programme peut utiliser différentes chaînes pour des langues aussi variées que l'anglais, le chinois, l'allemand, le finnois, l'italien, etc. Fondamentalement, cela fonctionne comme ceci :
44
5-`* ajoutez une `*`resourcestring`*` pour chaque légende (`*`caption`*`) ;`\
6-`* compilez pour obtenir les fichiers `*`.rst`*` et/ou `*`.po`*` (l'EDI peut le faire automatiquement) ;`\
7-`* créez un fichier traduit `*`.po`*` pour chaque langue (il existe des outils visuels libres pour ce faire) ;`\
8-`* utilisez les fonctions de l'unité `*`translations`*` de la LCL pour charger la bonne version de traduction au lancement du programme.`
5+```text
6+* ajoutez une resourcestring pour chaque légende (caption) ;
7+* compilez pour obtenir les fichiers .rst et/ou .po (l'EDI peut le faire automatiquement) ;
8+* créez un fichier traduit .po pour chaque langue (il existe des outils visuels libres pour ce faire) ;
9+* utilisez les fonctions de l'unité translations de la LCL pour charger la bonne version de traduction au lancement du programme.
10+```
911
1012## Date, heure et format de nombres
1113
1214Sous Linux, BSD et Mac OS X existent différentes valeurs "locales" définissant des éléments comme le format de l'heure et de la date ou le séparateur des milliers. Afin d'initialiser la RTL, vous devez inclure l'unité *clocale* dans la section *uses* de votre programme (fichier .lpr).
1315
1416## Les chaînes de ressources
1517
1618Par exemple :
1719
1820``` pascal
1921resourcestring
2022 Caption1 = 'Du texte';
2123 HelloWorld1 = 'Bonjour, monde';
2224```
2325
2426Elles sont comme des constantes de chaîne normales, ce qui veut dire que vous pouvez les assigner à n'importe quelle chaîne. Par exemple :
2527
2628``` pascal
2729Label1.Caption := HelloWorld1;
2830```
2931
3032Quand FPC les compile, il crée pour chaque unité un fichier **unitname.rst** contenant les données des chaînes de ressources (nom + contenu).
3133
3234## Les fichiers .po
3335
3436Il existe de nombreux outils visuels libres pour éditer les fichiers .po, qui sont de simples fichiers de texte comme les fichiers .rst, mais avec quelques options supplémentaires, comme un en-tête contenant des champs indiquant le nom de l'auteur, le type d'encodage, la langue et la date. Chaque installation de FPC met à disposition l'outil **rstconv** (Windows : rstconv.exe). Cet outil peut être utilisé pour convertir un fichier .rst en un fichier .po. L'EDI peut le faire automatiquement. Exemples d'outils libres : kbabel, po-auto-translator, poedit, virtaal.
3537
3638Virtaal a une mémoire de traduction contenant des paires source-cible pour les éléments que vous avez déjà traduits, et une fonction de suggestion de traduction qui montre les termes déjà traduits dans différents paquets de logiciels open source. Cette fonction peut vous permettre d'économiser beaucoup de travail et d'améliorer la cohérence des traductions.
3739
3840Exemple utilisant directement rstconv :
3941
40-` rstconv -i unit1.rst -o unit1.po`
42+```text
43+ rstconv -i unit1.rst -o unit1.po
44+```
4145
4246## La traduction
4347
4448Pour chaque langue, le fichier .po doit être copié et traduit. L'unité *translation* de la LCL utilise les codes habituels des langues(en=anglais, de=allemand, it=italien, ...) pour ses recherches. Par exemple, la traduction française de unit1.po serait unit1.fr.po. Pour atteindre ce résultat, le fichier unit1.po sera copié en unit1.fr.po, unit1.it.po, et toute autre langue que vous souhaitez supporter. Les traducteurs pourront alors éditer leur fichier .po spécifique.
4549
4650> **Note:** Pour les Brésiliens/Portugais: L'EDI de Lazarus et la LCL possèdent uniquement les traductions du Portugais Brésilien et ces fichiers ont pour extension 'pt_BR.po' (et non 'pt.po').
4751
4852## Les options de l'EDI pour la mise à jour des fichiers .po
4953
5054- L'unité contenant les ressources de chaînes doit être ajoutée au paquet ou au projet.
5155- Vous devez définir un chemin pour les .po, ce qui signifie un dossier qui leur est propre. Par exemple : créez un sous-répertoire *language* dans le répertoire du paquet/projet. Pour les projets, allez dans Projet \> Options du projet. Pour les paquets, allez dans Options \> Intégration à l'EDI.
5256
5357Quand ces options sont activées, l'EDI génère ou met à jour le fichier .po de base en utilisant les informations contenues dans les fichiers .rst et .lrt (l'outil rstconv n'est alors pas nécessaire). Le processus de mise à jour commence en collectant toutes les entrées existantes trouvées dans le fichier .po de base et dans les fichiers .rst et .lrt pour continuer en appliquant les caractéristiques qu'il trouve ensuite réactualisées dans n'importe quel fichier .xx.po de traduction.
5458
5559### Suppression des entrées obsolètes
5660
5761Les entrées du fichier .po de base qui ne sont pas trouvées dans les fichiers .rst et .lrt sont supprimées. De même, toutes les entrées trouvées dans les fichiers .xx.po non trouvées dans le fichier .po de base sont également enlevées. De cette façon, les fichiers .po ne sont pas encombrés avec des entrées obsolètes et les traducteurs n'ont pas à traduire les entrées qui ne sont pas utilisées.
5862
5963### Entrées en double
6064
6165Les entrées en double apparaissent quand pour quelque raison le même texte est utilisé pour différentes chaînes de ressources. Un exemple au hasard : dans le fichier lazarus/ide/lazarusidestrconst.pas pour la chaîne 'Gutter':
6266
6367``` pascal
6468 dlfMouseSimpleGutterSect = 'Gutter';
6569 dlgMouseOptNodeGutter = 'Gutter';
6670 dlgGutter = 'Gutter';
6771 dlgAddHiAttrGroupGutter = 'Gutter';
6872```
6973
7074Un fichier .rst converti pour ces chaînes de ressources serait similaire à ceci dans un fichier .po:
7175
72-`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
73-`msgid "Gutter"`\
74-`msgstr ""`\
75-`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
76-`msgid "Gutter"`\
77-`msgstr ""`\
78-`etc.`
76+```text
77+#: lazarusidestrconsts.dlfmousesimpleguttersect
78+msgid "Gutter"
79+msgstr ""
80+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
81+msgid "Gutter"
82+msgstr ""
83+etc.
84+```
7985
8086Les lignes commençant avec "#: " sont considérées comme des commentaires et les outils utilisés pour traduire ces entrées voient les lignes répétées msgid "Gutter" comme des entrées dupliquées produisant des erreurs ou des avertissements au chargement ou à la sauvegarde. Les entrées dupliquées sont considérées comme une éventualité normale dans un fichier .po et elles ont besoin d'avoir un certain contexte qui leur est attaché. Le mot clé *msgctxt* est utilisé pour ajouter un contexte aux entrées dupliquées et l'outil de mise à jour automatique utilise l'ID d'entrée (le texte à côté du préfixe "#: ") comme contexte. Pour l'exemple précédent serait produit quelque chose comme ceci :
8187
82-`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
83-`msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"`\
84-`msgid "Gutter"`\
85-`msgstr ""`\
86-`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
87-`msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"`\
88-`msgid "Gutter"`\
89-`msgstr ""`\
90-`etc.`\
88+```text
89+#: lazarusidestrconsts.dlfmousesimpleguttersect
90+msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
91+msgid "Gutter"
92+msgstr ""
93+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
94+msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
95+msgid "Gutter"
96+msgstr ""
97+etc.
98+```
9199
92100Pour les fichiers .xx.po traduits, l'outil automatique effectue une vérification supplémentaire : si l'entrée dupliquée était déjà traduite, la nouvelle entrée récupère l'ancienne traduction, apparaissant alors comme traduite automatiquement.
93101
94102Cette détection automatique des doublons n'est pas encore parfaite. La détection des doublons est effectuée quand des éléments sont ajoutés à la liste et il peut arriver que certaines entrées non traduites soient lues d'abord. Aussi se peut-il que plusieurs passes soient nécessaires pour récupérer tous les doublons traduits automatiquement par l'outil.
95103
96104### Entrées à l'origine de confusions possibles
97105
98106Les changements dans les chaînes de ressources affectent les traductions. Par exemple, si une chaîne de ressource a été initialement définie comme ceci :
99107
100108``` pascal
101109dlgEdColor = 'Syntax highlight';
102110```
103111
104112cela produirait une entrée .po similaire à ceci :
105113
106-`#: lazarusidestrconsts.dlgedcolor`\
107-`msgid "Syntax highlight"`\
108-`msgstr ""`
114+```text
115+#: lazarusidestrconsts.dlgedcolor
116+msgid "Syntax highlight"
117+msgstr ""
118+```
109119
110120qui, en cas de traduction en espagnol (cet exemple a été tiré de l'historique de Lazarus), peut résulter en :
111121
112-`#: lazarusidestrconsts.dlgedcolor`\
113-`msgid "Syntax highlight"`\
114-`msgstr "Color"`
122+```text
123+#: lazarusidestrconsts.dlgedcolor
124+msgid "Syntax highlight"
125+msgstr "Color"
126+```
115127
116128Supposons que, plus tard, la chaîne de ressources a été changée en :
117129
118130``` pascal
119131 dlgEdColor = 'Colors';
120132```
121133
122134l'entrée .po résultante pourrait devenir :
123135
124-`#: lazarusidestrconsts.dlgedcolor`\
125-`msgid "Colors"`\
126-`msgstr ""`
136+```text
137+#: lazarusidestrconsts.dlgedcolor
138+msgid "Colors"
139+msgstr ""
140+```
127141
128142Notez qu'alors que l'ID demeure le même (lazarusidestrconsts.dlgedcolor), la chaîne est passée de 'Syntax highlight' à 'Colors'. Comme la chaîne a déjà été traduite, l'ancienne traduction peut ne pas correspondre au nouveau sens recherché. Pour la nouvelle chaîne, il est probable que 'Colores' serait une meilleure traduction.
129143
130144L'outil de mise à jour automatique remarque cette situation et produit une entrée comme :
131145
132-`#: lazarusidestrconsts.dlgedcolor`\
133-`#, fuzzy`\
134-`#| msgid "Syntax highlight"`\
135-`msgctxt "lazarusidestrconsts.dlgedcolor"`\
136-`msgid "Colors"`\
137-`msgstr "Color"`
146+```text
147+#: lazarusidestrconsts.dlgedcolor
148+#, fuzzy
149+#| msgid "Syntax highlight"
150+msgctxt "lazarusidestrconsts.dlgedcolor"
151+msgid "Colors"
152+msgstr "Color"
153+```
138154
139155En terme de format de fichier .po, le préfixe "#," signifie que l'entrée est marquée avec un drapeau (*fuzzy*) si bien que les programmes de traduction peuvent présenter à l'utilisateur traducteur une interface visuelle adaptée à cet élément. Dans le cas qui nous intéresse, le drapeau signifierait que la traduction dans son état actuel est douteuse et nécessite d'être révisée plus attentivement par le traducteur. Le préfixe "#\|" indique ce qu'était la précédente chaîne non traduite de cette entrée et donne une indication au traducteur sur pourquoi cette entrée a été marquée comme source de confusions possibles.
140156
141157## Traduction de fiches, de modules de données et de cadres
142158
143159Quand l'option i18n est activée pour le projet/paquet, l'EDI produit automatiquement des fichiers .lrt pour chaque fiche. Il crée le fichier .lrt à l'enregistrement de l'unité. Par conséquent, si vous activez l'option pour la première fois, vous devez ouvrir chaque fiche une fois, la déplacer un petit peu à l'écran de sorte qu'elle soit modifiée, et l'enregistrer. Par exemple, si vous enregistrez une fiche *unit1.pas*, l'EDI crée un fichier *unit1.lrt*. A la compilation, l'EDI rassemble toutes les chaînes de tous les fichiers .lrt files et .rst dans un seul fichier .po (*projectname.po* ou *packagename.po*) dans le répertoire i18n.
144160
145161Pour les fiches qui sont à traduire à l'exécution, vous devez affecter un traducteur à *LRSTranslator* (défini dans *LResources*) dans la section *initialization* de l'une de vos unités.
146162
147163``` pascal
148164...
149165uses
150166 ...
151167 LResources;
152168...
153169...
154170initialization
155171 LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
156172```
157173
158174Néanmoins, il n'y a pas de classe TPoTranslator (i.e une classe qui traduit à partir des fichiers .po) disponible dans la LCL. Voici une implémentation possible (partiellement reprise de DefaultTranslator.pas dans la LCL): Le code suivant n'est plus nécessaire si vous utilisez une version 0.9.29 ou ultérieure de Lazarus. Ajoutez simplement l'unité *DefaultTranslator* dans la clause *uses*.
159175
160176``` pascal
161177unit PoTranslator;
162178
163179{$mode objfpc}{$H+}
164180
165181interface
166182
167183uses
168184 Classes, SysUtils, LResources, typinfo, Translations;
169185
170186type
171187
172188 { TPoTranslator }
173189
174190 TPoTranslator=class(TAbstractTranslator)
175191 private
176192 FPOFile:TPOFile;
177193 public
178194 constructor Create(POFileName:string);
179195 destructor Destroy;override;
180196 procedure TranslateStringProperty(Sender:TObject;
181197 const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
182198 end;
183199
184200implementation
185201
186202{ TPoTranslator }
187203
188204constructor TPoTranslator.Create(POFileName: string);
189205begin
190206 inherited Create;
191207 FPOFile:=TPOFile.Create(POFileName);
192208end;
193209
194210destructor TPoTranslator.Destroy;
195211begin
196212 FPOFile.Free;
197213 inherited Destroy;
198214end;
199215
200216procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
201217 const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
202218var
203219 s: String;
204220begin
205221 if not Assigned(FPOFile) then exit;
206222 if not Assigned(PropInfo) then exit;
207223{DO we really need this?}
208224 if Instance is TComponent then
209225 if csDesigning in (Instance as TComponent).ComponentState then exit;
210226{End DO :)}
211227 if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
212228 s:=FPOFile.Translate(Content, Content);
213229 if s<>'' then Content:=s;
214230end;
215231
216232end.
217233```
218234
219235Alternativement, vous pouvez transformer le fichier.po en .mo en utilisant *msgfmt* (n'est plus nécessaire non plus depuis la version 0.9.29) et utiliser simplement l'unité *DefaultTranslator*
220236
221237``` pascal
222238...
223239uses
224240 ...
225241 DefaultTranslator;
226242```
227243
228244qui recherchera automatiquement dans plusieurs emplacements standards un fichier .po (plus haute priorité) ou un fichier .mo (l'inconvénient est que vous devrez garder ensemble les fichiers .mo pour l'unité DefaultTranslator et les fichiers .po pour l'unité TranslateUnitResourceStrings.. Si vous utilisez l'unité *DefaultTranslator*, elle va essayer de détecter automatiquement la langue à partir de la variable d'environnement LANG (surchargeable en utilisant l'option --lang en ligne de commande), puis regardera dans les emplacements suivants pour la traduction (LANG représente la langue désirée ; l'extension peut être po ou mo) :
229245
230246- \<Répertoire de l'application\>/\<LANG\>/\<Nom de fichier de l'application\>.\<ext\>
231247- \<Répertoire de l'application\>/languages/\<LANG\>/\<Nom de fichier de l'application\>.\<ext\>
232248- \<Répertoire de l'application\>/locale/\<LANG\>/\<Nom de fichier de l'application\>.\<ext\>
233249- \<Répertoire de l'application\>/locale/LC_MESSAGES/\<LANG/\>\<Nom de fichier de l'application\>.\<ext\>
234250
235251Avec les systèmes du genre Unix, l'emplacement peut aussi ressembler à
236252
237253- /usr/share/locale/\<LANG\>/LC_MESSAGES/\<Nom de fichier de l'application\>.\<ext\>
238254
239255L'unité pourra aussi utiliser l'intitulé court de la langue (par exemple, s'il s'agit de "es_ES" ou "es_ES.UTF-8" et que le fichier n'existe pas, elle essayera aussi "es").
240256
241257## Traduction au début du programme
242258
243259Pour chaque fichier .po, vous devez appeler *TranslateUnitResourceStrings* de l'unité *translations* de la LCL. Par exemple :
244260
245261``` pascal
246262 {En premier lieu: ajoutez les unités "gettext" et "translations" à la clause uses}
247263 procedure TForm1.FormCreate(Sender: TObject);1
248264 var
249265 PODirectory, Lang, FallbackLang: String;
250266 begin
251267 PODirectory := '/path/to/lazarus/lcl/languages/';
252268 GetLanguageIDs(Lang, FallbackLang); // dans l'unité gettext
253269 TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
254270 MessageDlg('Titre', 'Texte', mtInformation, [mbOk, mbCancel, mbYes], 0);
255271 end;
256272```
257273
258274## Compilation des fichiers po dans l'exécutable
259275
260276Si vous ne voulez pas installer les fichiers .po, mais placer tous les fichiers dans l'exécutable, utilisez ce qui suit :
261277
262278- Créez une nouvelle unité (pas une fiche !).
263279- Convertissez le(s) fichier(s) .po en fichiers .lrs en utilisant le programme tools/lazres:
264280
265281 ./lazres unit1.lrs unit1.de.po
266282
267283Ceci va créer un fichier d'inclusion unit1.lrs commençant par
268284
269285``` pascal
270286LazarusResources.Add('unit1.de','PO',[
271287 ...
272288```
273289
274290- Ajoutez le code :
275291
276292``` pascal
277293uses LResources, Translations;
278294
279295resourcestring
280296 MyCaption = 'Caption';
281297
282298function TranslateUnitResourceStrings: boolean;
283299var
284300 r: TLResource;
285301 POFile: TPOFile;
286302begin
287303 r:=LazarusResources.Find('unit1.de','PO');
288304 POFile:=TPOFile.Create(False); //Si Full=True vous pouvez craindre un crash (problème #0026021)
289305 try
290306 POFile.ReadPOText(r.Value);
291307 Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
292308 finally
293309 POFile.Free;
294310 end;
295311end;
296312
297313initialization
298314 {$I unit1.lrs}
299315```
300316
301317- Appelez *TranslateUnitResourceStrings* au début du programme. Vous pouvez aussi choisir de le faire dans la section *initialization*.
302318
303319Malheureusement, ce code ne compilera pas avec Lazarus 1.2.2 et antérieurs.
304320
305321Pour ces versions anciennes, vous pouvez utiliser quelque chose comme cela :
306322
307323``` pascal
308324type
309325 TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);
310326
311327function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;
312328var
313329 LRes : TLResource;
314330 POFile : TPOFile = nil;
315331 SStream : TStringStream = nil;
316332begin
317333 Result := trResourceNotFound;
318334 LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
319335 if LRes <> nil then
320336 try
321337 SStream := TStringStream.Create(LRes.Value);
322338 POFile := TPoFile.Create(SStream, False);
323339 try
324340 if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess
325341 else Result := trTranslationError;
326342 except
327343 Result := trTranslationError;
328344 end;
329345 finally
330346 if Assigned(SStream) then SStream.Free;
331347 if Assigned(POFile) then POFile.Free;
332348 end;
333349end;
334350```
335351
336352Exemple d'utilisation :
337353
338354``` pascal
339355initialization
340356 {$I lclstrconsts.de.lrs}
341357 TranslateFromResource('lclstrconsts', 'de');
342358end.
343359```
344360
345361## Méthode indépendante de la plate-forme pour déterminer la langue système
346362
347363La fonction ci-dessous retourne une chaîne qui représente la langue de l'interface utilisateur. Elle tourne sous Linux, Mac OS X et Windows.
348364
349365``` pascal
350366uses
351367 Classes, SysUtils {ajoute dessunités supplémentaires nécessaires au code qui suit}
352368 {$IFDEF win32}
353369 , Windows
354370 {$ELSE}
355371 , Unix
356372 {$IFDEF LCLCarbon}
357373 , MacOSAll
358374 {$ENDIF}
359375 {$ENDIF}
360376 ;
361377```
362378
363379``` pascal
364380function GetOSLanguage: string;
365381{méthode indépendante de la plate-forme pour lire la langue de l'interface utilisateur}
366382var
367383 l, fbl: string;
368384 {$IFDEF LCLCarbon}
369385 theLocaleRef: CFLocaleRef;
370386 locale: CFStringRef;
371387 buffer: StringPtr;
372388 bufferSize: CFIndex;
373389 encoding: CFStringEncoding;
374390 success: boolean;
375391 {$ENDIF}
376392begin
377393 {$IFDEF LCLCarbon}
378394 theLocaleRef := CFLocaleCopyCurrent;
379395 locale := CFLocaleGetIdentifier(theLocaleRef);
380396 encoding := 0;
381397 bufferSize := 256;
382398 buffer := new(StringPtr);
383399 success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
384400 if success then
385401 l := string(buffer^)
386402 else
387403 l := '';
388404 fbl := Copy(l, 1, 2);
389405 dispose(buffer);
390406 {$ELSE}
391407 {$IFDEF LINUX}
392408 fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
393409 {$ELSE}
394410 GetLanguageIDs(l, fbl);
395411 {$ENDIF}
396412 {$ENDIF}
397413 Result := fbl;
398414end;
399415```
400416
401417## Traduction de l'EDI
402418
403419### Fichiers
404420
405421Les fichiers .po sont dans le répertoire source de Lazarus :
406422
407423- lazarus/languages =\> chaînes pour l'EDI
408424- lazarus/lcl/languages/ =\> chaînes pour la LCL
409425- lazarus/components/ideintf/languages/ =\> chaînes pour l'interface de l'EDI
410426
411427### Traducteurs
412428
413429- la traduction en allemand est maintenue par Joerg Braun.
414430- la traduction en finnois est maintenue par Seppo Suurtarla.
415431- la traduction en russe est maintenue par Maxim Ganetsky.
416432- la traduction en français est maintenue par Gilles Vasseur.
417433
418434Si vous voulez commencer une nouvelle traduction, demandez via le mailing si quelqu'un travaille déjà dessus.
419435
420436Veuillez lire attentivement :[[Lazarus Documentation/fr|Traduction/Internationalisation/Localisation]]
421437
422438## Voir aussi
423439
424440- [[IDE Development|IDE Development: Translations, i18n, lrt, po files]]
425441- [[Getting translation strings right/fr|Obtenir de bonnes traductions des chaînes]]
426442- [[Lazarus Documentation/fr|Traduction/Internationalisation/Localisation]]
427443
428444---
429445*From the Free Pascal wiki, page "Translations / i18n / localizations for programs/fr", as it stood on 2020-03-01 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history