Entwickler-Ecke

Open Source Units - VCLTranslator - eine XML basierte Übersetzungsunit


DeCodeGuru - Mi 26.10.05 00:47
Titel: VCLTranslator - eine XML basierte Übersetzungsunit
Guten abend alle zusammen,

nachdem ein Freund mir vor gut einem 3/4 Jahr von seiner Idee erzählte, eine Klasse zu schreiben, mit der es möglich sein soll, Programme, die mit Hilfe der VCL entwickelt wurden, zu übersetzen. Dabei sollen alle Sprachinformationen in einer XML-Datei abgelegt werden, damit jederman die Möglichkeit hat, Übersetzungen für Programme zu erstellen, die diese Klasse verwenden und damit das Feature anbieten. Aus Zeitgründen konnte er allerdings das Ding nicht schreiben und bat mich, diese Unit zu erstellen, da diese ja ggf. gebraucht werden könnte. Nun gut, ich habe kein 3/4 Jahr an der Unit geschrieben. Nein, ich habe mich letzten Samstag hingesetzt und habe mich dazu durchgerungen, das Teil zu programmieren. Gestern abend habe ich dann eine erste funktionstüchtige Alpha-Version fertiggestellt. Nachdem ich heute einige Korrekturen vorgenommen habe, denke ich, dass es nicht schlecht wäre, euch, als fachkundiges Publikum, die Unit vorzustellen und um Feedback zu bitten. ;)

Zu der Version/dem Status: Es handelt sich - wie bereits erwähnt - um eine Alphaversion, d.h. ich habe noch nicht alle Features implementiert. Außerdem bin ich bei manchen Sachen noch nicht ganz schlüssig, ob ich die so lassen soll oder doch noch ein bisschen ändere. Daher wäre mir Feedback sehr wichtig, zumal ich denke, dass die Unit - wenn sie denn fertig ist - durchaus auch gebrauchbar sein wird. *hoff*

Was bietet der VCLTranslator?
Mit Hilfe des VCLTranslators ist es möglich, die Eigenschaften von Controls einer Form zu ändern. Gemeint sind dabei nicht nur Eigenschaften wie Caption oder Text, sondern alle Eigenschaften, die das Control besitzt. Allerdings kann die Klasse (TTranslationManager) nicht alle Eigenschaften setzen, da es beispielsweise nicht möglich ist, eine Eigenschaft des Typs TStrings einfach mit einem Wert aus einer Variant-Variable zu besetzen. Da ich in der Unit nicht alle Eventualitäten abdecken kann, löse ich in dem Fall ein Ereignis aus (OnSelfTranslate), dass dem Programmierer alle nötigen Informationen liefert, damit dieser sich selber um das Setzen der Werte kümmern kann. Somit lässt sich rein prinzipiell doch alles setzen.

Während der Übersetzung wird bei bestimmten Ereignissen (Form, Control oder Eigenschaft nicht gefunden) eine Meldung im so genannten TranslationProtocol hinterlassen, damit man nach der Übersetzung genau nachvollziehen kann, was genau schief gegangen ist. Ein dieser Meldungen enthält einen Fehlercode, den Typ des zu setzenden Elementes (Form, Control, Property bzw. None) sowie den Namen des Elementes. Die Anzahl der Meldungen wird in der Variable HintCount imd TranslationProtocol gespeichert. Wenn eine dieser Meldungen gemacht wird und damit das oben genannte Ereignis eingetreten ist, wird der Übersetzungsvorgang aber nicht abgebrochen!

Echte Fehler, bei denen abgebrochen wird, treten nur dann auf, wenn entscheidene Knoten in der XML-Datei nicht existieren oder wenn die XML-Datei nicht vorhanden ist bzw. wenn das geparste XML-Dokument keine Daten enthält. In solchen Fällen wird der Dateneinlesevorgang abgebrochen. Informationen über den Abbruch erhält man über die Funktion GetLastError.

Eine wichtige Eigenschaft des VCLTranslator ist folgende: Da man ja rein prinzipiell alle Eigenschaften eines Controls verändern kann, also auch die Sichtbarkeit und Verfügbarkeit, lässt sich ja auch eine Menge murks mit entsprechenden XML-Dateien machen. Um dem ein Riegel vorzuschieben, habe ich eine Art Filter eingebaut (relativ rudimentär). Über die Funktion SetFilterRules kann man das Filtering aktivieren und als zweiten Parameter eine Stringlist übergeben, die alle Eigenschaftsnamen enthält, die gesetzt werden dürften. Will man nur Caption und Text zulassen, dann enthält die Stringslist eben nur diese Eigenschaften.


Folgende Regeln müssen für eine gültige XML-Datei gelten:

Hier mal eine Beispielsdatei, an Hand derer man die Struktur leicht erkennen kann:

XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
<?xml version="1.0" encoding="ISO-8859-1"?>
<translation language="Deutsch">
  <translator>
    <name>Jakob Wehner</name>
    <email>Jakob@wehner24.de</email>
    <webpage>http://www.wehner24.de</webpage>
  </translator>
  <information>
    <programversion min="0.1" max="0.9" /> //Verwendbarkeit in Programmversionen
    <date>01.01.2005</date> //Übersetzungsdatum
    <version>1.2</version> //Übersetzungsversion
    <status>alpha</status> //Übersetzungsstatus
  </information>
  <form name="MainForm">
    <control name="Button1">
      <property name="Caption" value="Hallo Welt" />
      <property name="Tag" value="1" />
    </control>
    <control name="Edit1">
      <property name="Text" value="Hallo Welt" />
    </control>
    <control name="Memo">
      <property name="Lines" value="Dies ist ein Delphi-Programm" />
    </control>
  </form>
  <form name="...

  </form>
</translation>

Jupp! Kommen wir zu dem etwas wesentlicheren Teil, nämlich der Unit und der wichtigsten Funktionen:
Die Klasse heißt TTranslationManager und beinhaltet alles, was man braucht. Mit Hilfe der Funktionen LoadXMLFile, die als Parameter einen Dateinamen besitzt, und SetXMLContents, die als Parameter eine Stringliste mit dem XML-Inhalt erwartet, kann man die Klasse entsprechend füllen lassen. Der Parsevorgang ist dem Ladevorgang angeschlossen; d.h. man muss sich darum nicht kümmern. Es Rückgabewert bekommt man entweder True, im Erfolgsfall, oder False, wenn was schief gegangen ist, zurück, wobei man über GetLastError Informationen über den Fehler, der zum Abbruch geführt hat, erhält.

Sollte das geklappt haben, dann kann man mittels TranslateForm (als Parameter ist die Form anzugeben) die angegebene Form übersetzen lassen. Als Rückgabewert erhält man entweder TS_OK, wenn alles einwandfrei übersetzt wurde, oder TS_SOME_ERRORS, wenn ein oder mehrere Fehler vorgekommen sind. Sollte man TS_SOME_ERRORS erhalten, so befinden sich im TranslationProtocol (entweder über GetTranslationProtocol oder über die Eigenschaft TranslationProtocol zu erhalten) Hinweise darüber, welches Element nicht übersetzt werden konnte. Darüber hinaus kann man auch TranslateAll verwenden, um alles zu übersetzen. Rückgabewerte und Handhabung wie bei TranslateForm.

Die Funktionen GetAuthorInfo und GetTranslationInfo geben einem eine Struktur mit den jeweils gewollten Informationen zurück.

Zu guter letzt bleibt noch die Funktion SetFilterRules, die zwei Parameter erwartet. Mit dem ersten Parameter (Boolean) kann man angeben, ob man das Filtering aktiviern möchte (standardmäßig deaktiviert). Wenn man das möchte, gibt man als zweiten Parameter eine Stringlist an, in der Eigenschaften enthalten sind. (Wenn man Filtering nicht möchte, einfach beim zweiten Parameter nil übergeben).

Vorraussetzungen für die Verwendung der Unit weiß ich gerade gar nicht so genau. :oops: Aber ich verwende halt das Interface IXMLDocument zum Parsen der XML-Datei. Sollte IMHO ab Delphi 6 vorhanden sein, bin mir aber nicht sicher.

Und schließlich die Unit:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590:
591:
592:
593:
594:
595:
596:
597:
598:
599:
600:
601:
602:
603:
604:
605:
606:
607:
608:
609:
610:
611:
612:
613:
614:
615:
616:
617:
618:
619:
620:
621:
622:
623:
624:
625:
626:
627:
628:
629:
630:
631:
632:
633:
634:
635:
636:
637:
638:
639:
640:
641:
642:
643:
644:
645:
646:
647:
648:
649:
650:
651:
652:
653:
654:
655:
656:
657:
658:
659:
660:
661:
662:
663:
664:
665:
666:
667:
668:
669:
670:
671:
672:
673:
674:
675:
676:
677:
678:
679:
680:
681:
682:
683:
684:
685:
686:
687:
688:
689:
690:
691:
692:
693:
694:
695:
696:
697:
698:
699:
700:
701:
702:
703:
704:
705:
706:
707:
708:
709:
710:
711:
712:
713:
714:
715:
716:
717:
718:
719:
720:
721:
722:
723:
724:
725:
726:
727:
728:
729:
730:
731:
732:
733:
734:
735:
736:
737:
738:
739:
{************************************************}
{*                                              *}
{*            VCL Translation Manager           *}
{*                                              *}
{************************************************}
{*                                              *}
{*  Version: 0.1 a2    Status: alpha            *}
{*                                              *}
{*  Copyright (c) 2005  Jakob Wehner            *}
{*  eMail: Jakob@wehner24.de                    *}
{*  URL: http://www.wehner24.de                 *}
{*                                              *}
{*  Dank an Florian Volk für die Idee und die   *}
{*  Grundkonzeption                             *}
{*  URL: http://www.florian-volk.net            *}
{*                                              *}
{*  Lizenz: GNU General Public License          *}
{*  http://www.gnu.de/gpl-ger.html              *}
{*                                              *}
{*  Dateiname: VCLTranslator.pas                *}
{*  Letzte Änderung: 25.10.2005                 *}
{*                                              *}
{************************************************}
{*                                              *}
{*  Changelog:                                  *}
{*  * Version 0.1 alpha 2 [25.10.2005]          *}
{*    - Property Forms entfernt                 *}
{*    - Property TranslationProtocol            *}
{*      hinzugefügt                             *}
{*    - Korrektur in Funktion SetFilterRules    *}
{*    - Funktion GetAuthorInfo hinzugefügt      *}
{*    - Funktion GetTranslationInfo hinzugefügt *}
{*    - Ereignis OnSelfTranslate erweitert      *}
{*    - Änderungen in den Funktionen            *}
{*      NodeExists, ReadForms, ReadControls,    *}
{*      ReadProperties                          *}
{*    - einige Kommentare hinzugefügt           *}
{*    - Fehlerbehandlung in LoadXMLFile         *}
{*      erweitert                               *}
{*                                              *}
{*  * Version 0.1 alpha 1 [24.10.2005]          *}
{*    - erste Basisversion fertiggestellt       *}
{*                                              *}
{************************************************}

unit VCLTranslator;

interface

uses
  Forms, Classes, SysUtils, XMLIntf, XMLDoc, TypInfo;

const
  //EC = Error code
  EC_DOCUMENT_EMPTY             = 1;  //das geparste Dokument ist leer
  EC_NO_TRANSLATION_NODE        = 2;  //es existiert kein Knoten "translation"
  EC_NO_FORM_NODE               = 4;  //es existiert kein Knoten "Form"
  EC_FILE_NOT_FOUND             = 8;  //Angegebene Datei existiert nicht
  EC_FILE_NOT_READABLE          = 16//Datei ist nicht lesbar
  EC_FORM_NOT_FOUND             = 32//zu der angegebenen Form wurde keine
                                      //gültige Übersetzungsdefinition geliefert
  EC_CONTROL_NOT_FOUND          = 64//das Control existiert nicht
  EC_PROPERTY_NOT_FOUND         = 128//Control besitzt die Eigenschaft nicht
  EC_PROPERTY_COULD_NOT_BET_SET = 256//Eigenschaft konnte nicht gesetzt werden
  EC_NO_RULE_LIST               = 512//es wurden keine Filterregeln übergeben

  //TS = Translation status
  TS_OK           = 1//es sind keine Fehler aufgetreten
  TS_SOME_ERRORS  = 2//es sind ein oder mehr Fehler aufgetreten -> Protokoll

type
  TErrorInfo = record
    ErrorCode: Cardinal;
    Description: String;
  end;

  TProperty = record
    Name: String;
    Value: Variant;
  end;

  TProperties = array of TProperty;

  TTransControl = record
    Name: String;
    Properties: TProperties;
    PropertyCount: Integer;
  end;

  TTransControls = array of TTransControl;

  TTransForm = record
    Name: String;
    Controls: TTransControls;
    ControlCount: Integer;
  end;

  TTransForms = array of TTransForm;

  TTranslator = record
    InfoAvailable: Boolean;
    Name,
    eMail,
    WebPage: String;
  end;

  TTranslationInfo = record
    InfoAvailable: Boolean;
    MinVersion,
    MaxVersion,
    Version,
    Date,
    Status: String;
  end;

  TItemType = (itNone, itForm, itControl, itProperty);

  TProtocolItem = record
    ErrorCode: Cardinal;
    ItemType: TItemType;
    ItemName: String;
  end;

  TTranslationProtocol = record
    HintCount: Integer;
    Protocol: array of TProtocolItem;
  end;

type
  TTranslationEvent = procedure (Sender: TObject; Form: TForm; Control: TObject;
    ControlName, PropertyName: String; Value: Variant) of Object;

type
  TTranslationManager = class(TObject)
  private
    fForms: TTransForms;
    fFormCount: Integer;
    fTranslator: TTranslator;
    fInformation: TTranslationInfo;
    fLanguage: String;
    fFileName: String;
    fFiltering: Boolean;
    fFilterRules: TStrings;
    fLastError: TErrorInfo;
    fTranslationProtocol: TTranslationProtocol;
    fOnSelfTranslate: TTranslationEvent;
    fXMLContents: TStrings;
    fXMLDocument: IXMLDocument;

    procedure SetLastError(ErrorCode: Cardinal; Description: String);
    procedure AddProtocolItem(AErrorCode: Cardinal; AItemType: TItemType;
        AItemName: String);
    function NodeExists(NodeName: Stringconst RootNode: IXMLNode): Boolean;
    function GetElementCount(const RootNode: IXMLNode; Name,
        Attribute: String): Integer;
    function GetPropertyCount(const RootNode: IXMLNode): Integer;
    procedure ClearStuff;

    function Parse: Boolean;
    procedure ReadForms(const RootNode: IXMLNode);
    procedure ReadControls(const RootNode: IXMLNode; var Form: TTransForm);
    procedure ReadProperties(const RootNode: IXMLNode;
        var Control: TTransControl);
    procedure ReadTranslator;
    procedure ReadInformation;
  public
    constructor Create;

    function LoadXMLFile(FileName: String): Boolean;
    function SetXMLContents(const XMLContents: TStringList): Boolean;
    procedure Clear;
    function TranslateForm(Form: TForm): Byte;
    function TranslateAll: Byte;
    function GetLastError: TErrorInfo;
    function GetTranslationProtocol: TTranslationProtocol;
    function GetAuthorInfo: TTranslator;
    function GetTranslationInfo: TTranslationInfo;
    function SetFilterRules(Filtering: Boolean;
        const AllowedProperties: TStrings): Boolean;

    property FormCount: Integer read fFormCount;
    property Filtering: Boolean read fFiltering;
    property FilterRules: TStrings read fFilterRules;
    property Information: TTranslationInfo read fInformation;
    property Language: String read fLanguage write fLanguage;
    property Translator: TTranslator read fTranslator;
    property TranslationProtocol: TTranslationProtocol
        read fTranslationProtocol;
    property XMLFile: String read fFileName;

    property OnSelfTranslate: TTranslationEvent read fOnSelfTranslate
        write fOnSelfTranslate;
  end;

implementation

constructor TTranslationManager.Create;
begin
  inherited Create;

  //XML-Dokument und Listen erzeugen
  fXMLContents := TStringList.Create;
  fXMLDocument := NewXMLDocument;
  fFilterRules := TStringList.Create;

  //Alles leeren
  Clear;
end;

procedure TTranslationManager.SetLastError(ErrorCode: Cardinal;
    Description: String);
begin
  //Informationen über den letzten Fehler setzen
  fLastError.ErrorCode := ErrorCode;
  fLastError.Description := Description;
end;

procedure TTranslationManager.AddProtocolItem(AErrorCode: Cardinal;
    AItemType: TItemType; AItemName: String);
begin
  //Meldung hinzufügen
  Inc(fTranslationProtocol.HintCount);
  SetLength(fTranslationProtocol.Protocol, fTranslationProtocol.HintCount);

  //Meldung mit Informationen befüllen
  with fTranslationProtocol.Protocol[High(fTranslationProtocol.Protocol)] do
  begin
    ErrorCode := AErrorCode;
    ItemType := AItemType;
    ItemName := AItemName;
  end;
end;

function TTranslationManager.NodeExists(NodeName: String;
    const RootNode: IXMLNode): Boolean;
var
  i: Integer;
begin
  Result := False;
  //Alle Knoten im RootNode durchgehen und überprüfen, ob es einen Knoten mit
  //dem Name NodeName gibt. Wenn ja, dann Schleifenabbruch und Result = True!
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    if RootNode.ChildNodes[i].NodeName = LowerCase(NodeName) then
    begin
      Result := True;
      Break;
    end;
end;

function TTranslationManager.GetElementCount(const RootNode: IXMLNode;
    Name, Attribute: String): Integer;
var
  i, fCount: Integer;
begin
  fCount := 0;
  //Alle Knoten im RootNode durchgehen und bei jedem Knoten mit dem Name
  //"Name" und dem Attribut "Attribute" den Zähler fCount um 1 erhöhen
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    if (RootNode.ChildNodes[i].NodeName = LowerCase(Name))
        and (RootNode.ChildNodes[i].HasAttribute(LowerCase(Attribute))) then
      Inc(fCount);

   Result := fCount;
end;

function TTranslationManager.GetPropertyCount(const RootNode: IXMLNode):
    Integer;
var
  i, fCount: Integer;
begin
  fCount := 0;
  //Gleiches Spiel wie bei GetElementCount; nur für die Knoten "Property"
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    if (RootNode.ChildNodes[i].NodeName = 'property')
        and (RootNode.ChildNodes[i].HasAttribute('name'))
        and (RootNode.ChildNodes[i].HasAttribute('value')) then
      Inc(fCount);
  Result := fCount;
end;

procedure TTranslationManager.ClearStuff;
begin
  //XML-Dokument leeren und deaktivieren
  fXMLDocument.XML.Text := '';
  fXMLDocument.Active := False;

  //Informationen über Formdefinitionen leeren
  SetLength(fForms, 0);
  fFormCount := 0;

  //Sprachinformation leeren
  fLanguage := '';

  //Informationen über den Übersetzer leern
  with fTranslator do
  begin
    InfoAvailable := False;
    eMail := '';
    Name := '';
    WebPage := '';
  end;

  //Übersetzungsinformationen leeren
  with fInformation do
  begin
    InfoAvailable := False;
    MaxVersion := '';
    MinVersion := '';
    Version := '';
    Date := '';
    Status := '';
  end;

  //Übersetzungsprotokoll leeren
  fTranslationProtocol.HintCount := 0;
  SetLength(fTranslationProtocol.Protocol, 0);
end;

procedure TTranslationManager.ReadForms(const RootNode: IXMLNode);
var
  i, fCounter: Integer;
begin
  //Anzahl der Formen auslesen und in der Struktur setzen
  fFormCount := GetElementCount(RootNode, 'form''name');
  SetLength(fForms, fFormCount);

  fCounter := 0;
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    //Form wird nur akzeptiet, wenn das Attribut "name" gesetzt wurde
    if (RootNode.ChildNodes[i].NodeName = 'form')
        and (RootNode.ChildNodes[i].HasAttribute('name')) then
    begin
      fForms[fCounter].Name := RootNode.ChildNodes[i].Attributes['name'];

      //Controls einlesen
      ReadControls(RootNode.ChildNodes[i], fForms[fCounter]);

      Inc(fCounter);
    end;
end;

procedure TTranslationManager.ReadControls(const RootNode: IXMLNode;
    var Form: TTransForm);
var
  i, fCounter: Integer;
begin
  //Anzahl der Controls auslesen und in der Struktur setzen
  Form.ControlCount := GetElementCount(RootNode, 'control''name');
  SetLength(Form.Controls, Form.ControlCount);

  fCounter := 0;
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    //Control wird nur akzeptiert, wenn das Attribut "name" gesetzt ist
    if (RootNode.ChildNodes[i].NodeName = 'control')
        and (RootNode.ChildNodes[i].HasAttribute('name')) then
    begin
      Form.Controls[fCounter].Name := RootNode.ChildNodes[i].Attributes['name'];

      //Eigenschaften einlesen
      ReadProperties(RootNode.ChildNodes[i], Form.Controls[fCounter]);

      Inc(fCounter);
    end;
end;

procedure TTranslationManager.ReadProperties(const RootNode: IXMLNode;
    var Control: TTransControl);
var
  i, fCounter: Integer;
begin
  //Anzahl der Properties einlesen und entsprechend die Struktur anpassen
  Control.PropertyCount := GetPropertyCount(RootNode);
  SetLength(Control.Properties, Control.PropertyCount);

  fCounter := 0;
  for i := 0 to RootNode.ChildNodes.Count - 1 do
    //Property nur akzeptiert, wenn die Attribute "name" und "value" existieren
    if (RootNode.ChildNodes[i].NodeName = 'property')
        and (RootNode.ChildNodes[i].HasAttribute('name'))
        and (RootNode.ChildNodes[i].HasAttribute('value')) then
    begin
      Control.Properties[fCounter].Name :=
          RootNode.ChildNodes[i].Attributes['name'];
      Control.Properties[fCounter].Value :=
          RootNode.ChildNodes[i].Attributes['value'];

      Inc(fCounter);
    end;
end;

procedure TTranslationManager.ReadTranslator;
begin
  //Existiert der Knoten "Translator"?
  if NodeExists('translator',fXMLDocument.ChildNodes['translation']) then
    //Wenn ja, dann lies alle vorhandenen Informationen aus
    with fXMLDocument.ChildNodes['translation'do
    begin
      fTranslator.InfoAvailable := True;
      if NodeExists('name', ChildNodes['translator']) then
        fTranslator.Name :=
            ChildNodes['translator'].ChildNodes['name'].NodeValue
      else
        fTranslator.Name := 'unknown';

      if NodeExists('email', ChildNodes['translator']) then
        fTranslator.eMail :=
            ChildNodes['translator'].ChildNodes['email'].NodeValue
      else
        fTranslator.eMail := 'unknown';

      if NodeExists('webpage', ChildNodes['translator']) then
        fTranslator.WebPage :=
            ChildNodes['translator'].ChildNodes['webpage'].NodeValue
      else
        fTranslator.WebPage := 'unknown';
    end
  else //wenn nicht, dann setze die Übersetzerinfos auf "unknown"
    with fTranslator do
    begin
      InfoAvailable := False;
      Name := 'unknown';
      eMail := 'unknown';
      WebPage := 'unknown';
    end;
end;

procedure TTranslationManager.ReadInformation;
begin
  //Existiert der Knoten "Information"?
  if NodeExists('information', fXMLDocument.ChildNodes['translation']) then
    //wenn ja, dann lies alle vorhandenen Informationen aus
    with fXMLDocument.ChildNodes['translation'do
    begin
      fInformation.InfoAvailable := True;
      if NodeExists('programversion', ChildNodes['information']) then
      begin
        if ChildNodes['information'].ChildNodes['programversion'].
            HasAttribute('min'then
          fInformation.MinVersion :=
              ChildNodes['information'].ChildNodes['programversion'].
                  Attributes['min']
        else
          fInformation.MinVersion := '';

        if ChildNodes['information'].ChildNodes['programversion'].
            HasAttribute('max'then
          fInformation.MaxVersion :=
              ChildNodes['information'].ChildNodes['programversion'].
                  Attributes['max']
        else
          fInformation.MaxVersion := '';
      end
      else
      begin
        fInformation.MinVersion := '';
        fInformation.MaxVersion := '';
      end;

      if NodeExists('date', ChildNodes['information']) then
        fInformation.Date :=
            ChildNodes['information'].ChildNodes['date'].NodeValue
      else
        fInformation.Date := '';

      if NodeExists('version', ChildNodes['information']) then
        fInformation.Version :=
            ChildNodes['information'].ChildNodes['version'].NodeValue
      else
        fInformation.Version := '';

      if NodeExists('status', ChildNodes['information']) then
        fInformation.Status :=
            ChildNodes['information'].ChildNodes['status'].NodeValue
      else
        fInformation.Status := '';
    end
  else // wenn nicht, dann leere alle Informationen
    with fInformation do
    begin
      InfoAvailable := False;
      MaxVersion := '';
      MinVersion := '';
      Version := '';
      Date := '';
      Status := '';
    end;
end;

function TTranslationManager.Parse: Boolean;
begin
  Result := True;

  //Alles leeren
  ClearStuff;

  //XMLContents parsen
  fXMLDocument.XML.Text := fXMLContents.Text;
  fXMLDocument.Active := True;

  {Folgendes muss gelten, damit es weitergehen kann:
   - XML-Dokument darf nicht leer sein
   - Knoten "translation" muss existieren
   - Knoten "form" muss mindestens einmal vorkommen}

  if fXMLDocument.IsEmptyDoc then
  begin
    SetLastError(EC_DOCUMENT_EMPTY, 'Das Dokument ist leer!');
    Result := False;
    Exit;
  end;

  if not NodeExists('translation', fXMLDocument.Node) then
  begin
    SetLastError(EC_NO_TRANSLATION_NODE, 'Kein Knoten "Translation" gefunden!');
    Result := False;
    Exit;
  end;

  if not NodeExists('form', fXMLDocument.ChildNodes['translation']) then
  begin
    SetLastError(EC_NO_FORM_NODE, 'Kein Knoten "Form" gefunden!');
    Result := False;
    Exit;
  end;

  //Sprache einlesen
  if fXMLDocument.ChildNodes['translation'].HasAttribute('language'then
    fLanguage := fXMLDocument.ChildNodes['translation'].Attributes['language']
  else
  begin
    //Aus dem Dateinamen auslesen
    if fFileName <> '' then
    begin
      fLanguage := ExtractFileName(fFileName);
      Delete(fLanguage, Pos(ExtractFileExt(FLanguage), fLanguage),
              Length(ExtractFileExt(fLanguage)));
    end
    else //Kein Fehler, da auch ohne die Information gearbeitet werden kann
      fLanguage := 'unknown';
  end;

  //Formen einlesen
  ReadForms(fXMLDocument.ChildNodes['translation']);

  //Informationen über den Übersetzer auslesen
  ReadTranslator;

  //Informationen über die Übersetzung auslesen
  ReadInformation;
end;


function TTranslationManager.LoadXMLFile(FileName: String): Boolean;
begin
  //Existiert die Datei nicht?
  if not FileExists(FileName) then
  begin
    SetLastError(EC_FILE_NOT_FOUND, 'Datei nicht gefunden!');
    Result := False;
    Exit;
  end;

  //Dateiname setzen und Inhalt der Datei laden
  fFileName := FileName;
  try
    fXMLContents.LoadFromFile(FileName);
  except //ist beim Laden der Datei etwas schief gegangen?
    fFileName := '';
    SetLastError(EC_FILE_NOT_READABLE, 'Datei ist nicht lesbar!');
    Result := False;
    Exit;
  end;

  Result := Parse;
end;

function TTranslationManager.SetXMLContents(const XMLContents: TStringList):
    Boolean;
begin
  fFileName := '';

  //Inhaltsprüfung unnötig, da dies in Parse() übernommen wird
  fXMLContents.Text := XMLContents.Text;

  Result := Parse;
end;

procedure TTranslationManager.Clear;
begin
  //Die "übrigen" Informationen löschen
  ClearStuff;

  //Dateiname zurücksetzen und ungeparste Übersetzungsinformationen löschen
  fXMLContents.Clear;
  fFileName := '';

  //Filtering zurücksetzen
  fFiltering := False;
  fFilterRules.Clear;
end;

function TTranslationManager.TranslateForm(Form: TForm): Byte;

  function IsRuleAllowed(PropertyName: String): Boolean;
  begin
    Result := False;
    if fFilterRules.IndexOf(PropertyName) <> -1 then
      Result := True;
  end;

var
  i, j, cPos: Integer;
  PropInfo: PPropInfo;
begin
  Result := TS_OK;

  cPos := -1//In dynamischen Arrays als Index nicht möglich
  for i := 0 to fFormCount - 1 do
    if fForms[i].Name = Form.Name then
      cPos := i;

  //Wurde die Form nicht gefunden?
  if cPos = -1 then
  begin
    AddProtocolItem(EC_FORM_NOT_FOUND, itForm, Form.Name);
    Result := TS_SOME_ERRORS;
    Exit;
  end;

  for i := 0 to fForms[cPos].ControlCount - 1 do
    //Existiert das Control
    if Form.FindComponent(fForms[cPos].Controls[i].Name) <> nil then
      for j := 0 to fForms[cPos].Controls[i].PropertyCount - 1 do
      begin
        //Ist der Filter aktiv? Wenn ja, darf die Eigenschaft gesetzt werden?
        if (fFiltering = False) xor
            ((fFiltering = True) and
              IsRuleAllowed(fForms[cPos].Controls[i].Properties[j].Name)) then
        begin
          PropInfo := GetPropInfo(Form.FindComponent(fForms[cPos].
                            Controls[i].Name).ClassInfo,
                        fForms[cPos].Controls[i].Properties[j].Name);
          //Existiert die Eigenschaft?
          if (PropInfo <> niland (PropInfo^.SetProc <> nilthen
            //Kann die Eigenschaft verarbeitet werden?
            if PropInfo^.PropType^.Kind in [tkInteger, tkChar, tkFloat,
               tkString, tkWChar, tkLString, tkWString, tkVariant, tkInt64] then
            begin
              SetPropValue(Form.FindComponent(fForms[cPos].Controls[i].Name),
                fForms[cPos].Controls[i].Properties[j].Name,
                fForms[cPos].Controls[i].Properties[j].Value);
            end
            else //Programmierer muss sich selbst drum kümmern
            begin
              if Assigned(fOnSelfTranslate) then
                fOnSelfTranslate(Self, Form,
                    Form.FindComponent(fForms[cPos].Controls[i].Name),
                    fForms[cPos].Controls[i].Name,
                    fForms[cPos].Controls[i].Properties[j].Name,
                    fForms[cPos].Controls[i].Properties[j].Value);

              AddProtocolItem(EC_PROPERTY_COULD_NOT_BET_SET, itProperty,
                fForms[cPos].Name + '.' + fForms[cPos].Controls[i].Name + '.' +
                fForms[cPos].Controls[i].Properties[j].Name);
              Result := TS_SOME_ERRORS;
            end
          else
          begin
            AddProtocolItem(EC_PROPERTY_NOT_FOUND, itProperty,
              fForms[cPos].Name + '.' + fForms[cPos].Controls[i].Name + '.' +
              fForms[cPos].Controls[i].Properties[j].Name);
            Result := TS_SOME_ERRORS;
          end//if (PropInfo <> nil) ...
        end//if (fFiltering = False) ...
      end //for j := 0 to ...
    else //if Form.FindComponent...
    begin
      AddProtocolItem(EC_CONTROL_NOT_FOUND, itControl,
        fForms[cPos].Name + '.' +fForms[cPos].Controls[i].Name);
      Result := TS_SOME_ERRORS;
    end;
end;

function TTranslationManager.TranslateAll: Byte;
var
  i: Integer;
begin
  Result := TS_OK;
  //Alle Formen durchgehen und versuchen zu übersetzen
  for i := 0 to Screen.FormCount - 1 do
    if TranslateForm(Screen.Forms[i]) <> TS_OK then
      Result := TS_SOME_ERRORS;
end;

function TTranslationManager.GetLastError: TErrorInfo;
begin
  //den letzten Fehler zurückgeben
  Result := fLastError;
end;

function TTranslationManager.GetTranslationProtocol: TTranslationProtocol;
begin
  //das Übersetzungsprotokoll zurückgeben
  Result := fTranslationProtocol;
end;

function TTranslationManager.GetAuthorInfo: TTranslator;
begin
  //Informationen über den Übersetzer zurückgeben
  Result := fTranslator;
end;

function TTranslationManager.GetTranslationInfo: TTranslationInfo;
begin
  //Informationen über die Übersetzung zurückgeben
  Result := fInformation;
end;

function TTranslationManager.SetFilterRules(Filtering: Boolean;
    const AllowedProperties: TStrings): Boolean;
begin
  Result := True;

  fFiltering := Filtering;
  if fFiltering then
  begin
    //Sind die Filterregeln übergeben worden?
    if not Assigned(AllowedProperties) then
    begin //wenn nicht, dann...
      SetLastError(EC_NO_RULE_LIST, 'Keine Filterregeln definiert!');
      Result := False;
      Exit;
    end;

    //Filterreglen intern setzen
    fFilterRules.Text := AllowedProperties.Text;
  end;
end;

end.


Hier [http://www.wehner24.de/stuff/VCLTranslator_with_demo.zip] nochmal eine Zip-Datei mit einer kleinen Demoanwendung.

So, jetzt bin ich zu müde, um noch weiter zu schwafeln. Werde morgen nochmal über den Beitrag schauen, ob auch alles drin ist oder ob etwas fehlt. :roll:

//Edit: Habe jetzt doch noch einen winzigen Punkt gefunden, den ich erwähnen möchte. Ich stelle dir Unit vorerst unter die GNU Gernel Public License. Ob das dauerhaft so bleiben wird, weiß ich noch nicht. Wenn nicht, dann werde ich die Verwendung aber nicht einschränken, sondern lockern, da ich glaube, dass es für den ein oder anderen, der die Unit verwenden möchte, doch angenehmer ist, wenn er sein Programm nicht zwingend OpenSource machen muss. :)

Gutes Nächtle wünsch ich euch allen

Jakob


Martin1966 - Mi 26.10.05 08:04

Guten Morgen!

Sehr interessant... werde mir die Unit mal anschauen uns ausprobieren. Eine Frage habe ich bereits schon. Wird die XML Datei jedesmal neu geladen wenn ich ein Form anzeige und die Texte dort drin übersetzen möchte?

Lg Martin


digi_c - Mi 26.10.05 08:09

Das klingt super :D und sogar daran gedacht das man Eigenschaften wie cm,inch umschalten kann! Wenn ich nur die zeit hätte das mal auszuprobieren :/


DeCodeGuru - Mi 26.10.05 08:28

Hallöchen,
Martin1966 hat folgendes geschrieben:
Eine Frage habe ich bereits schon. Wird die XML Datei jedesmal neu geladen wenn ich ein Form anzeige und die Texte dort drin übersetzen möchte?

Nein. Die XML-Datei wird ausschließlich geladen und geparst, wenn du die Funktion LoadXMLFile oder SetXMLContents aufrufst. Die angeschlossene Funktion Parse liest alle lesbaren und benötigten Informationen in eine Struktur ein. Du kannst also nach dem Einlesevorgang gezielt Formen übersetzen, ohne dass der Inhalt der XML-Datei noch einmal eingelesen wird.

An der Stelle fällt mir auf, dass ich doch noch etwas vergessen habe. Es existiert auch eine Prozedur Clear, die das Objekt reinigt, also den geparsten Inhalt löscht. Wichtig ist hier, dass der Aufruf diese Prozedur auch die Filterregeln löscht! Wenn man aber nur eine neue XML-Datei lädt, um halt eine andere Sprache zu laden, dann muss man sich um das Säubern der Klasse nicht kümmern, da ich am Anfang des Parsevorgangs die Klasse säubern lasse. Hierbei werden aber die Filterregeln erhalten!


Dezipaitor - Fr 03.03.06 22:35

hi

das sieht ja schonmal ganz gut aus.

Habe aber noch einige Vorschläge:

+Bitte WIDESTRING (also Unicode) statt String verwenden! (in XML ist das UTF-8 und nicht ISO-8859-1)
sonst werden viele Sprachen ausgeschlossen.


+Es sollte die Möglichkeit bestehen, Formulare übersetzen zu lassen,
die erst zur Laufzeit erstellt werden. Z.b.:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
var Form : TMyForm;
begin
  Form := TMyform.Create(Self);
  Translator.TranslateForm(Form);
  Form.ShowModal;
  Form.Free;
end;

Oder habe ich das übersehen?


+Man sollte auch normale Strings im Quelltext übersetzen können.

Delphi-Quelltext
1:
 Mystring := Translator.Translate('String_Name','Voreingestellter String, wenn nicht in XML gefunden');                    


+Man kann auch Standard Strings übersetzen, wie sie in Buttons (OK, Abbrechen) von MessageDLG und so
vorkommen (siehe dazu http://www.swissdelphicenter.ch/de/showcode.php?id=285)

+Zu LoadXMLFile sollte es auch LoadXMLFromStream geben, denn so kann man auch die XML Dateien
als Resource einbetten lassen und per TResourceStream auslesen. So gibt es keine extra Dateien.

+Schön wäre es, wenn die deutschen Fehlermeldungen und andere Strings zumindest oben als Konstanten
definiert werden. So kann man es schnell ändern.
Dasselbe gilt für die Namen der XML-Tags.

+Eine Kommentierung der public Methoden und Eigenschaften ist eigentlich Pflicht ;-)
Es geht dabei nicht nur darum, was sie machen, vielmehr geht es um mögliche Fehler und deren Rückgabewerte.

+Hast du schonmal überprüft, ob die Klasse Speicherleaks hat? (z.b. mit FastMem oder MemCheck)
Ich habe kein Destroy gefunden, deshalb werden wohl mindestens 3 Instanzen in Create zwar erzeugt,
aber nicht mehr freigegeben.

+Die XML sieht gut aus. Weißt du denn, das es für XML Dateien Syntaxprüfer gibt?
Man erstellt eine Syntaxdatei (die DTD [http://de.wikipedia.org/wiki/XML_DTD]), bindet sie in das XML ein
und lässt dann dem DOM-Parser damit überprüfen, ob der Syntax stimmt. Somit ist der Aufbau in Ordnung und es
können nur noch semantische Fehler enthalten sein.


So das wars erstmal. Ich hoffe ich habe dich jetzt nicht überrant ;-)
Aber das war meine konstruktive Kritik.


DeCodeGuru - Sa 04.03.06 09:48

Hi,

die oben gepostete Version ist schon etwas älter. Auf der Festplatte habe ich schon eine wesentlich neuere, die auch einige deiner Features bietet, Version. Allerdings ist die halbfertig und derzeit fehlt mir wirklich die Zeit, mich damit zu beschäftigen (Abi, Referate und Klausuren vorm Abi vertragen sich nicht allzu gut mit der Zeitplanung!). Nachdem das aber vorbei ist, habe ich erstmal wieder mehr Zeit und dann kann ich mich darum kümmern. ;)

Was mich wundert ist allerdings, dass in der oben genannten Unit der Destruktor fehlt. Der ist bei mir hier - und das komische ist, dass er auch in den älteren Versionen zu finden ist - vorhanden. :?

Viele Grüße

Jakob


Dezipaitor - Sa 11.03.06 19:12

hmm alle Kontaktversuche sind fehlgeschlagen.
Schicke mir doch bitte nur kurz mal deine neuste Version!


Dezipaitor - So 12.03.06 01:24

Habe nun mal etwas kleines vorbereitet:


XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE translation SYSTEM "language.dtd">
<translation version="1.0">
  <!-- eine Übersetzungssektion
    enthält den Windows SprachCode (ID) und den Namen -->

  <language id="49" value="Deutsch">
    <!--
     Dank an den Übersetzer
    -->

    <translator>
      <name></name>
      <email></email>
      <webpage></webpage>
    </translator>
    <!--
    Einige Informationen zu dieser Übersetzung
    -->

    <information>
      <programversion max="0.9" min="0.1"></programversion>
      <!--Verwendbarkeit in Programmversionen-->
      <date>10.03.2006</date>
      <!--Uebersetzungsdatum-->
      <version>0.5</version>
      <!--Uebersetzungsversion-->
      <status>alpha</status>
      <!--Uebersetzungsstatus-->
    </information>
    <!--
    Ein Formular zum Übersetzen
    -->

    <form name="MainForm">
      <!--
      Die Inhalte, welche Übersetzt werden sollen
      -->

      <control name="Button1">
        <!--durch diese Art, koennen auch Umbrueche verwendet werden-->
        <property name="Caption">deutscher Text</property>
        <property name="Tag">123</property>
      </control>
      <control name="Edit1">
        <property name="Text">Text</property>
      </control>
      <control name="Memo">
        <property name="Lines">Mehrzeilger Text
        Zeile 2    </property>
      </control>
    </form>
    <!-- Texte, die in Ressourcen wie SysUtils oder Consts vorhanden sind, werden hier Übersetzt.-->
    <resources>
      <text id="123">text</text>
    </resources>
    <!-- Texte, die im Quelltext vorkommen werden hier uebersetzt-->
    <string-resources>
      <text id="123">text</text>
    </string-resources>
  </language>
  <!--Weitere Uebersetzungen-->
  <language id="30" value="Englisch">
    <translator>
      <name></name>
      <email></email>
      <webpage></webpage>
    </translator>
    <information>
      <programversion max="0.9" min="0.1"></programversion>
      <!--Verwendbarkeit in Programmversionen-->
      <date>10.03.2006</date>
      <!--Uebersetzungsdatum-->
      <version>0.5</version>
      <!--Uebersetzungsversion-->
      <status>alpha</status>
      <!--Uebersetzungsstatus-->
    </information>
    <form name="MainForm">
      <control name="Button1">
        <property name="Caption">English text</property>
        <property name="Tag"></property>
      </control>
      <control name="Edit1">
        <property name="Text">Text</property>
      </control>
      <control name="Memo">
        <property name="Lines">Mehrzeilger Text
      Zeile 2
      </property>
      </control>
    </form>
  </language>
</translation>


und die dazugehörige DTD:

XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
<?xml version="1.0" encoding="UTF-8"?>
<!--
Root Element of translation file.
Contains at least one translation section (language)

Attributes:
version - contains version number
-->

<!ELEMENT translation (language+)>
<!ATTLIST translation
  version CDATA #REQUIRED>


<!--
language is a translation section

Subelements:
translator  - optional
information - optional
form      - 0..infinite
resources   - 0..infinite
string-resources - 0..infinite

Attributes:
id   - contains windows language code
value - contains language name

-->

<!ELEMENT language (translator?,information?,form*,resources*,string-resources*) >
<!ATTLIST language
    id CDATA #REQUIRED
  value CDATA #REQUIRED>

<!--
translator contains some information to translator
-->

<!ELEMENT translator (name,email?,webpage?)>

<!--
information contains some information about translation
-->

<!ELEMENT information (programversion,date,version,status?)>

<!--
name of author
-->

<!ELEMENT name (#PCDATA) >

<!--
email of author
-->

<!ELEMENT email (#PCDATA) >
<!--
webppage of author
-->

<!ELEMENT webpage (#PCDATA) >

<!--
contains information about compatibility 

Attributes:
min - min version of program required
max - max version of program allowed

-->

<!ELEMENT programversion EMPTY>
<!ATTLIST programversion
    min CDATA #REQUIRED
  max CDATA #REQUIRED>
  
  
<!--
creation date of this translation
-->

<!ELEMENT date (#PCDATA) >

<!--
version of this translation 
-->

<!ELEMENT version (#PCDATA) >
<!--
status of translation.
can be a string.
-->

<!ELEMENT status (#PCDATA) >


<!--
form contains translation data to
translate a delphi form

SubElements
control - at least one

Attributes:
name - name of form
-->

<!ELEMENT form (control+) >
<!ATTLIST form
  name CDATA #REQUIRED>
  
<!--
control identifies a control on form
to be translated

SubElements:
property : at least one

Attributes:
name : name of control on form
-->

<!ELEMENT control (property+) >
<!ATTLIST control
  name CDATA #REQUIRED>
  

<!--
property determines the controls property
to be changed

Attributes:
name - name of property

Value:
value that should be assigned to property.
You must make sure that there will be no type
mismatch.
-->

<!ELEMENT property (#PCDATA) >
<!ATTLIST property
  name CDATA #REQUIRED>
  
  
<!--
resources contains strings that are assigned
to internal string constants (like in consts and sysutils)

SubElements:
text - 0..infinite
-->

<!ELEMENT resources (text*) >


<!--
string-resources contains strings that are used
in source (translated by a function call)

SubElements:
text - 0..infinite
-->

<!ELEMENT string-resources (text*) >

<!--
text contains a string that must be translates

Attributes:
id - contains identifier (string or integer)
   This value must be unique!
-->

<!ELEMENT text (#PCDATA) >
<!ATTLIST text
  id CDATA #REQUIRED>


DeCodeGuru - So 12.03.06 09:14

Hallo,

es ist schön, dass du dich für diese Unit interessierst und Vorschläge machst. Ich bin auch absolut offen für diese und gerne bereit, Verbesserungen vorzunehmen. Ich bitte nur um etwas Verständnis, dass es mir im Moment (und das wird sich bis Mitte April nicht ändern) absolut keine Zeit bleibt, mich auf diese Unit zu konzentrieren. Danach werde ich schauen, was sich machen lässt. Von daher ein bisschen mehr Geduld. :)

Viele Grüße

Jakob


Dezipaitor - Di 11.04.06 23:06

ist noch Abitur?


Spaceguide - Di 11.04.06 23:40

Hmm, wäre es nicht praktischer im Quellcode/DFM einen sprachunabhängigen String zu hinterlegen und in der Übersetzungstabelle nur einen Verweis auf die sprachabhängigen Strings zu schaffen?

<key value="_CANCEL">
<string language="english">Cancel</string>
<string language="german">Abbrechen</string>
</key>

So wie ich das sehe muss ich bei deiner Methode für jeden Cancel-Knopf die Übersetzungen in alle Sprachen neu eingeben.


MrKnogge - Do 13.04.06 15:45

Dafür müsstest du intern jedem Cancel-Button die Übersetzung zuweisen, was bei obiger Unit automatisch gescheht.


Dezipaitor - Do 13.04.06 20:44

user profile iconSpaceguide hat folgendes geschrieben:
Hmm, wäre es nicht praktischer im Quellcode/DFM einen sprachunabhängigen String zu hinterlegen und in der Übersetzungstabelle nur einen Verweis auf die sprachabhängigen Strings zu schaffen?

<key value="_CANCEL">
<string language="english">Cancel</string>
<string language="german">Abbrechen</string>
</key>

So wie ich das sehe muss ich bei deiner Methode für jeden Cancel-Knopf die Übersetzungen in alle Sprachen neu eingeben.


Ja, wenn es automatisch gemacht werden soll. Für jedes Formular gibt es eben einen Eintrag in der form section.
Wenn du gleiche Strings auch gleich zum übersetzen zwingen willst, dann musst du das manuell machen:
Für all diese buttons:

CancelButton.Caption := _T('CANCEL_ID','Abbruch');

In dem Fall muss keine Eigenschaft für diesen Button in der XML gesetzt werden.


Spaceguide - Do 13.04.06 20:48

Also ich schreibe auf meine Captions z.B. immer "_CANCEL" und habe eine Routine, die im FormCreate aufgerufen wird und rekursiv alle Controls des Forms durchgeht und die Captions und Hints übersetzt.


Dezipaitor - Do 13.04.06 21:19

und die Übersetzung aus XML ließt?


Spaceguide - Do 13.04.06 21:46

Nach dem oben genannten Schema.


Dezipaitor - Do 13.04.06 21:48

stellst du deine Unit zum DL ?


Spaceguide - Do 13.04.06 22:10

Geht leider schlecht, ist Teil einer größeren Applikation und läuft nicht ohne ein Dutzend anderer Units. Ich kann aber beschreiben was sie macht:

XML parsen und in Hashtable einfügen
Funktion Translate(TForm) bereitstellen, welche rekursiv alle gefundenen Captions, Hints, die Columns bei Listviews etc. übersetzt
Funktion Translate(string,[a,b,c,d,e,...]) bereitstellen, zum manuellen Übersetzen im Quellcode mit Platzhalter, z.B. _MYTEXT = Sie haben %1 Dateien geöffnet. %1 wird dann durch den optionalen Parameter a ersetzt.

Nur mal so als Anregung.


Dezipaitor - Do 13.04.06 22:32

hmm
finde das leider nicht so gut.

Ich habe einige hundert Strings übersetzt und nichtmal 1% davon waren gleich.

Wenn man viele gleiche Strings hat, gibt es jedoch einen Ausweg:
Resourcenstring

man setzt

Delphi-Quelltext
1:
2:
3:
4:
resourcestring
   XYZ = 'Cancel';
...
mybuttons.caption := XYZ;


Die Resourcenstrings werden dann automatisch übersetzt.


Spaceguide - Do 13.04.06 22:42

Also die Methode mit einem sprachunabhängigen Schlüssel und dann einer Sprachdatei bzw. mehrerer Sprachdateien für jede Zielsprache ist eigentlich die absolute Standardmethode, wird sehr oft verwendet und in der Vorlesung "Erw. Benutzeroberflächen", die ich gehört habe, empfohlen :)

Ich habe hier im Durchschnitt zwei Zuordnungen pro hinterlegtem Schlüssel bei ~1500 Schlüsseln.


Dezipaitor - Fr 14.04.06 17:02

user profile iconSpaceguide hat folgendes geschrieben:
Also die Methode mit einem sprachunabhängigen Schlüssel und dann einer Sprachdatei bzw. mehrerer Sprachdateien für jede Zielsprache ist eigentlich die absolute Standardmethode, wird sehr oft verwendet und in der Vorlesung "Erw. Benutzeroberflächen", die ich gehört habe, empfohlen :)

Ich habe hier im Durchschnitt zwei Zuordnungen pro hinterlegtem Schlüssel bei ~1500 Schlüsseln.


Unit oder FH?

Was heißt Zuordnung? zwei Sprachen, oder zwei wirklich gleichlautende Texte? Das zweite kann ich mir nicht vorstellen, da ja viel zu viele doppelte Texte verwendet werden würde. Was ist das denn dann für eine Anwendung?

Naja ich habe schon erfahren müssen, das nicht alles was in den Vorlesungen gepedigt wird, auch wirklich so gut anwendbar ist.


für die Standardtexte, wie OK, Abbrechen, Ja, Nein kann man sowieso die Resourcenkonstanten aus const.pas verwenden.


Delphi-Quelltext
1:
2:
OKButton.Caption := SMsgDlgOK;
CancelButton.Caption := SMsgDlgCancel


Diese Konstanten werden auch für die MessageBoxes verwendet.

Ich werde mal sehen, ob man nicht in die XML Datei Verknüpfungen zu anderen Strings einbetten kann.

Etwa so. Wäre das was?

XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
<form ...>
<control name="OKButton">
  <property name="caption" link="SMsgDlgOK"/>
</control>
<form ...>

<resource-strings>
  <text id="SMsgDlgOK">O&K</text>
</resource-strings>


Spaceguide - Fr 14.04.06 17:56

FH natürlich. Viel zu praxisorientiert für Uni ;-)

Java z.B. hat eine eingebaute Unterstützung für beliebige Sprachen durch sogenannte Ortseinstellungen. Dort sehen die Sprachedateien so aus:

Login.Name = Name:
Login.Password = Password:
Login.Title = Login to server xyz
Login.Ok = Ok
Login.Abort = Abort

Ist eigentlich ja schon ausreichend und lässt XML schon als Overkill erscheinen ;-)


Dezipaitor - Sa 15.04.06 14:50

was genau ist login?


Spaceguide - Mo 17.04.06 15:55

In dem Beispiel auf den Folien war ein Login-Dialog abgebildet. Die Captions hiessen dann z.b. Login.Name und wurden zur Laufzeit übersetzt.


Andidreas - Sa 30.09.06 23:51

Tolle Sache. Wie kann man aber in einer TRadioGroup die Items.Strings mit in die Übersetzung einbeziehen. Bin nämlich gerade dabei ein Tool zu schreiben, dass eine *.dfm einliest und daraus ein XML-Grundgerüst erstellt.

So, die Frage hat sich erledigt.