Autor |
Beitrag |
DeCodeGuru
Beiträge: 1333
Erhaltene Danke: 1
Arch Linux
Eclipse
|
Verfasst: 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:
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. 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:
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:
|
unit VCLTranslator;
interface
uses Forms, Classes, SysUtils, XMLIntf, XMLDoc, TypInfo;
const EC_DOCUMENT_EMPTY = 1; EC_NO_TRANSLATION_NODE = 2; EC_NO_FORM_NODE = 4; EC_FILE_NOT_FOUND = 8; EC_FILE_NOT_READABLE = 16; EC_FORM_NOT_FOUND = 32; EC_CONTROL_NOT_FOUND = 64; EC_PROPERTY_NOT_FOUND = 128; EC_PROPERTY_COULD_NOT_BET_SET = 256; EC_NO_RULE_LIST = 512; TS_OK = 1; TS_SOME_ERRORS = 2; 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: String; const 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;
fXMLContents := TStringList.Create; fXMLDocument := NewXMLDocument; fFilterRules := TStringList.Create;
Clear; end;
procedure TTranslationManager.SetLastError(ErrorCode: Cardinal; Description: String); begin fLastError.ErrorCode := ErrorCode; fLastError.Description := Description; end;
procedure TTranslationManager.AddProtocolItem(AErrorCode: Cardinal; AItemType: TItemType; AItemName: String); begin Inc(fTranslationProtocol.HintCount); SetLength(fTranslationProtocol.Protocol, fTranslationProtocol.HintCount);
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; 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; 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; 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 fXMLDocument.XML.Text := ''; fXMLDocument.Active := False;
SetLength(fForms, 0); fFormCount := 0;
fLanguage := '';
with fTranslator do begin InfoAvailable := False; eMail := ''; Name := ''; WebPage := ''; end;
with fInformation do begin InfoAvailable := False; MaxVersion := ''; MinVersion := ''; Version := ''; Date := ''; Status := ''; end;
fTranslationProtocol.HintCount := 0; SetLength(fTranslationProtocol.Protocol, 0); end;
procedure TTranslationManager.ReadForms(const RootNode: IXMLNode); var i, fCounter: Integer; begin fFormCount := GetElementCount(RootNode, 'form', 'name'); SetLength(fForms, fFormCount);
fCounter := 0; for i := 0 to RootNode.ChildNodes.Count - 1 do if (RootNode.ChildNodes[i].NodeName = 'form') and (RootNode.ChildNodes[i].HasAttribute('name')) then begin fForms[fCounter].Name := RootNode.ChildNodes[i].Attributes['name'];
ReadControls(RootNode.ChildNodes[i], fForms[fCounter]);
Inc(fCounter); end; end;
procedure TTranslationManager.ReadControls(const RootNode: IXMLNode; var Form: TTransForm); var i, fCounter: Integer; begin Form.ControlCount := GetElementCount(RootNode, 'control', 'name'); SetLength(Form.Controls, Form.ControlCount);
fCounter := 0; for i := 0 to RootNode.ChildNodes.Count - 1 do if (RootNode.ChildNodes[i].NodeName = 'control') and (RootNode.ChildNodes[i].HasAttribute('name')) then begin Form.Controls[fCounter].Name := RootNode.ChildNodes[i].Attributes['name'];
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 Control.PropertyCount := GetPropertyCount(RootNode); SetLength(Control.Properties, Control.PropertyCount);
fCounter := 0; 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 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 if NodeExists('translator',fXMLDocument.ChildNodes['translation']) then 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 with fTranslator do begin InfoAvailable := False; Name := 'unknown'; eMail := 'unknown'; WebPage := 'unknown'; end; end;
procedure TTranslationManager.ReadInformation; begin if NodeExists('information', fXMLDocument.ChildNodes['translation']) then 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 with fInformation do begin InfoAvailable := False; MaxVersion := ''; MinVersion := ''; Version := ''; Date := ''; Status := ''; end; end;
function TTranslationManager.Parse: Boolean; begin Result := True;
ClearStuff;
fXMLDocument.XML.Text := fXMLContents.Text; fXMLDocument.Active := True;
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;
if fXMLDocument.ChildNodes['translation'].HasAttribute('language') then fLanguage := fXMLDocument.ChildNodes['translation'].Attributes['language'] else begin if fFileName <> '' then begin fLanguage := ExtractFileName(fFileName); Delete(fLanguage, Pos(ExtractFileExt(FLanguage), fLanguage), Length(ExtractFileExt(fLanguage))); end else fLanguage := 'unknown'; end;
ReadForms(fXMLDocument.ChildNodes['translation']);
ReadTranslator;
ReadInformation; end;
function TTranslationManager.LoadXMLFile(FileName: String): Boolean; begin if not FileExists(FileName) then begin SetLastError(EC_FILE_NOT_FOUND, 'Datei nicht gefunden!'); Result := False; Exit; end;
fFileName := FileName; try fXMLContents.LoadFromFile(FileName); except 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 := '';
fXMLContents.Text := XMLContents.Text;
Result := Parse; end;
procedure TTranslationManager.Clear; begin ClearStuff;
fXMLContents.Clear; fFileName := '';
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; for i := 0 to fFormCount - 1 do if fForms[i].Name = Form.Name then cPos := i;
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 if Form.FindComponent(fForms[cPos].Controls[i].Name) <> nil then for j := 0 to fForms[cPos].Controls[i].PropertyCount - 1 do begin 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); if (PropInfo <> nil) and (PropInfo^.SetProc <> nil) then 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 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; end; end else 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; 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 Result := fLastError; end;
function TTranslationManager.GetTranslationProtocol: TTranslationProtocol; begin Result := fTranslationProtocol; end;
function TTranslationManager.GetAuthorInfo: TTranslator; begin Result := fTranslator; end;
function TTranslationManager.GetTranslationInfo: TTranslationInfo; begin Result := fInformation; end;
function TTranslationManager.SetFilterRules(Filtering: Boolean; const AllowedProperties: TStrings): Boolean; begin Result := True;
fFiltering := Filtering; if fFiltering then begin if not Assigned(AllowedProperties) then begin SetLastError(EC_NO_RULE_LIST, 'Keine Filterregeln definiert!'); Result := False; Exit; end;
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.
//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
Beiträge: 1068
Win 2000, Win XP
Delphi 7, Delphi 2005
|
Verfasst: 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
Beiträge: 1905
W98, XP
D7 PE, Lazarus, WinAVR
|
Verfasst: Mi 26.10.05 08:09
Das klingt super und sogar daran gedacht das man Eigenschaften wie cm,inch umschalten kann! Wenn ich nur die zeit hätte das mal auszuprobieren :/
|
|
DeCodeGuru
Beiträge: 1333
Erhaltene Danke: 1
Arch Linux
Eclipse
|
Verfasst: 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
Beiträge: 220
|
Verfasst: 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 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
Beiträge: 1333
Erhaltene Danke: 1
Arch Linux
Eclipse
|
Verfasst: 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
Beiträge: 220
|
Verfasst: Sa 11.03.06 19:12
hmm alle Kontaktversuche sind fehlgeschlagen.
Schicke mir doch bitte nur kurz mal deine neuste Version!
|
|
Dezipaitor
Beiträge: 220
|
Verfasst: So 12.03.06 01:24
Habe nun mal etwas kleines vorbereitet:
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"> <language id="49" value="Deutsch"> <translator> <name></name> <email></email> <webpage></webpage> </translator> <information> <programversion max="0.9" min="0.1"></programversion> <date>10.03.2006</date> <version>0.5</version> <status>alpha</status> </information> <form name="MainForm"> <control name="Button1"> <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> <resources> <text id="123">text</text> </resources> <string-resources> <text id="123">text</text> </string-resources> </language> <language id="30" value="Englisch"> <translator> <name></name> <email></email> <webpage></webpage> </translator> <information> <programversion max="0.9" min="0.1"></programversion> <date>10.03.2006</date> <version>0.5</version> <status>alpha</status> </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:
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"?>
<!ELEMENT translation (language+)> <!ATTLIST translation version CDATA #REQUIRED>
<!ELEMENT language (translator?,information?,form*,resources*,string-resources*) > <!ATTLIST language id CDATA #REQUIRED value CDATA #REQUIRED>
<!ELEMENT translator (name,email?,webpage?)>
<!ELEMENT information (programversion,date,version,status?)>
<!ELEMENT name (#PCDATA) >
<!ELEMENT email (#PCDATA) >
<!ELEMENT webpage (#PCDATA) >
<!ELEMENT programversion EMPTY> <!ATTLIST programversion min CDATA #REQUIRED max CDATA #REQUIRED>
<!ELEMENT date (#PCDATA) >
<!ELEMENT version (#PCDATA) >
<!ELEMENT status (#PCDATA) >
<!ELEMENT form (control+) > <!ATTLIST form name CDATA #REQUIRED>
<!ELEMENT control (property+) > <!ATTLIST control name CDATA #REQUIRED>
<!ELEMENT property (#PCDATA) > <!ATTLIST property name CDATA #REQUIRED>
<!ELEMENT resources (text*) >
<!ELEMENT string-resources (text*) >
<!ELEMENT text (#PCDATA) > <!ATTLIST text id CDATA #REQUIRED> |
|
|
DeCodeGuru
Beiträge: 1333
Erhaltene Danke: 1
Arch Linux
Eclipse
|
Verfasst: 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
Beiträge: 220
|
Verfasst: Di 11.04.06 23:06
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: 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
Beiträge: 76
Win XP, Win 7
Delphi 7 Prof, Delphi 2005 PE, C# (VS 2008)
|
Verfasst: 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
Beiträge: 220
|
Verfasst: Do 13.04.06 20:44
Spaceguide 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
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: 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
Beiträge: 220
|
Verfasst: Do 13.04.06 21:19
und die Übersetzung aus XML ließt?
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: Do 13.04.06 21:46
Nach dem oben genannten Schema.
|
|
Dezipaitor
Beiträge: 220
|
Verfasst: Do 13.04.06 21:48
stellst du deine Unit zum DL ?
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: 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
Beiträge: 220
|
Verfasst: 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
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: 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.
|
|
|