Autor Beitrag
DeCodeGuru
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 1333
Erhaltene Danke: 1

Arch Linux
Eclipse
BeitragVerfasst: Mi 26.10.05 00:47 
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:
  • der Rootknoten ist heißt immer "translation" mit dem Attribut "language", das als Wert die Sprache besitzt. Existiert das Attribut nicht, dann wird der Dateiname (ohne .xml) als Sprache verwendet, was aber nicht immer schön aussieht.
  • Innerhalb von Translation muss es mindestens einen Knoten namens "form" geben, in dem die Informationen über die Controls dieser Form gespeichert sind. Diese Knoten müssen das Attribut "name" haben, das den Formnamen enthält. Exsitiert das Attribut nicht, werden die Informationen über die Form nicht gelesen.
  • In Translation kann es die knoten "information" und "translator" geben, in denen die Informationen über die Übersetzung und den Übersetzer enthalten sind. Diese sind nicht notwendig, aber zu empfehlen.
  • Alle angegebenen Controls müssen das Attribut "name" haben. Ansonsten wird es übergangen!

Hier mal eine Beispielsdatei, an Hand derer man die Struktur leicht erkennen kann:
ausblenden 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:
ausblenden volle Höhe 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 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


Zuletzt bearbeitet von DeCodeGuru am Sa 07.01.06 01:42, insgesamt 3-mal bearbeitet
Martin1966
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1068

Win 2000, Win XP
Delphi 7, Delphi 2005
BeitragVerfasst: 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

_________________
Ein Nutzer der Ecke ;-)
digi_c
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1905

W98, XP
D7 PE, Lazarus, WinAVR
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 1333
Erhaltene Danke: 1

Arch Linux
Eclipse
BeitragVerfasst: 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!

_________________
Viele Grüße
Jakob
Dezipaitor
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: 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.:
ausblenden 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.
ausblenden 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 www.swissdelphicente.../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), 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 1333
Erhaltene Danke: 1

Arch Linux
Eclipse
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: Sa 11.03.06 19:12 
hmm alle Kontaktversuche sind fehlgeschlagen.
Schicke mir doch bitte nur kurz mal deine neuste Version!
Dezipaitor
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: So 12.03.06 01:24 
Habe nun mal etwas kleines vorbereitet:

ausblenden volle Höhe 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:
ausblenden volle Höhe 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 1333
Erhaltene Danke: 1

Arch Linux
Eclipse
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: Di 11.04.06 23:06 
ist noch Abitur?
Spaceguide
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 76

Win XP, Win 7
Delphi 7 Prof, Delphi 2005 PE, C# (VS 2008)
BeitragVerfasst: Do 13.04.06 15:45 
Dafür müsstest du intern jedem Cancel-Button die Übersetzung zuweisen, was bei obiger Unit automatisch gescheht.

_________________
MfG MrKnogge
Dezipaitor
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: Do 13.04.06 21:19 
und die Übersetzung aus XML ließt?
Spaceguide
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: Do 13.04.06 21:46 
Nach dem oben genannten Schema.
Dezipaitor
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: Do 13.04.06 21:48 
stellst du deine Unit zum DL ?
Spaceguide
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 220



BeitragVerfasst: 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
ausblenden Delphi-Quelltext
1:
2:
3:
4:
resourcestring
   XYZ = 'Cancel';
...
mybuttons.caption := XYZ;


Die Resourcenstrings werden dann automatisch übersetzt.
Spaceguide
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: 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.