How can I translate the Firemonkey's Resource Strings? - delphi

I'm иrazilian and I need the Firemonkey's Resource Strings in my language, for example when I use the dialogs. I couldn't find a way to translate it. Does someone know how to do it?

What you need is something like this, but for FMX. In a quick search, the only file that I've found in help for Delphi XE2 is FMX.Consts. You take that file, translate it, and then put the translated file in your project.
Take care when Delphi got an update. The original file can be changed and you will need to update your translation. Also, you will probably want to change any others files that have Resource Strings, and are in use by your project.
Finally, I'm not expert in this, but if you are planing use multi-language, this could be not the better approach.

Translating FMX forms is done with the TLang component, although that won't work for most dialogs.

You can change dialogs using FMX.Consts.pas, but it fixes the language during compilation.
If you want to check the language version of the host OS in runtime, you should correct FMX.Platform.Android.pas or FMX.Platform.iOS.pas.
For Android, in FMX.Platform.Android.pas, in procedure TPlatformAndroid.MessageDialog... find ButtonCaptions and surround it with your own function, for example: ZSTranslate( ButtonCaptions[B]).
Declare ZSTranslate in the following way:
function ZSTranslate(txt: String):String;
var
LocaleSvc: IFMXLocaleService;
begin
LocaleSvc := TPlatformServices.Current.GetPlatformService(IFMXLocaleService) as IFMXLocaleService;
result:=txt;
if LocaleSvc.GetCurrentLangID ='your_language_two_letter_id' then
begin
if txt= 'Yes' then
result := 'yes in your language'
else
if txt= 'No' then
result := 'no in your language'
else
if txt= 'Confirm' then
result := 'confirm in your language'
else
if txt= 'Cancel' then
result := 'cancel in your language';
end
end;
Place ZSTranslate somewhere above TPlatformAndroid.MessageDialog in a copy of FMX.Platform.Android.pas, and add this corrected version of FMX.Platform.Android.pas to your project.
Note that above example is very simple, and as far as I remember there is unsolved case in Embarcadero quality system, suggesting translate method here (so TLang should work ok). I did not try with translate, my version did the job (as there are just a few button labels in dialogs and I wanted only two different languages).
For iOS you should look in FMX.Platform.iOS.pas for function TPlatformCocoaTouch.MessageDialog. Please note, that there are two overloaded versions. There are also MsgTitles and ButtonCaptions in iOS, as dialogs at iOS show captions.
PS. For Polish I had to correct also GetCurrentLangID method, because it always returned 'en' - please double-check the result for your language.
The versions that worked for me:
in FMX.Platform.iOS.pas:
function TPlatformCocoaTouch.GetCurrentLangID: string;
var
lngs : NSArray;
CurrentLocale: NSLocale;
LanguageISO: NSString;
begin
lngs := TNSLocale.OCClass.preferredLanguages;
LanguageISO:= TNSString.Wrap(lngs.objectAtIndex(0));
//CurrentLocale := TNSLocale.Wrap(TNSLocale.OCClass.currentLocale);
//LanguageISO := TNSString.Wrap(CurrentLocale.objectForKey((NSLocaleLanguageCode as ILocalObject).GetObjectID));
Result := UTF8ToString(LanguageISO.UTF8String);
if Length(Result) > 2 then
Delete(Result, 3, MaxInt);
end;
in FMX.Platform.Android.pas:
function TPlatformAndroid.GetCurrentLangID: string;
var
Locale: JLocale;
begin
Locale := TJLocale.JavaClass.getDefault;
Result := JStringToString(Locale.getLanguage);//getISO3Language); //zs
if Length(Result) > 2 then
Delete(Result, 3, MaxInt);
end;

Related

OpenOffice Desktop Instance can not be created (com object)

I have some problems to create an instance of the StarOffice Desktop object.
I used the standard construct below but whenever it comes to the line: StarDesktop := StarOffice.CreateInstance('com.sun.star.frame.Desktop');
My StarDesktop Variant stays unassigned. I am pretty sure that the code is ok until there but perhaps something with the OpenOffice installation is messed up.
Is there a way to check the com objects or did somebody had the same problem and could solve it...
uses
ComObj;
procedure OpenOfficeDocument;
var
StarOffice: Variant;
StarDesktop: Variant;
begin
StarOffice := CreateOleObject('com.sun.star.ServiceManager');
StarDesktop := StarOffice.CreateInstance('com.sun.star.frame.Desktop');
// StarDesktop is always "unassigned"
....
Yes, I know. I should have stated more clearly that I am too 100% sure that it would work normally in a correct environment.
But my question is what could be the cause why it doesn't work. Why the 'com.sun.star.frame.Desktop' instance is unassigned. I have no option/way to debug it...
And it is a bit unfair to vote me down, I researched for one hour without finding something to explain why it could not work.
Or how and where to check if something is wrong with the Office installation (I uninstalled and reinstalled it twice already"
Again, I know this will work for others and normally would work for me, but something is wrong at my system and I would like to know some help to point me in the direction what could be wrong in the system (and not in the code example...)
is OpenOffice installed on client?
doesn't throw any exception?
I'm using Bernard Marcelly's Delphi 7 OOo tool and as can you see his code like that;
var
OpenOffice, StarDesktop: Variant;
...
OpenOffice:= CreateOleObject('com.sun.star.ServiceManager');
if isNullEmpty(OpenOffice) then Raise Exception.Create('OpenOffice connection is impossible');
StarDesktop:= OpenOffice.createInstance('com.sun.star.frame.Desktop');
if isNullEmpty(Result) then Raise Exception.Create(Format('Impossible to create service : %s', ['com.sun.star.frame.Desktop']));
...
'some constants converted to string'
So, if StarDesktop is null, possible can not access Oo Desktop service. If OpenOffice installed properly some features may be missing, options have to set.
This works for me (in my application):
class procedure TOpenOffice.Connect;
begin
if IsConnected then
Exit;
try
FServiceManager := CreateOleObject('com.sun.star.ServiceManager');
except
FServiceManager := Null;
end;
if VarIsNull(FServiceManager) then
raise EOpenOfficeException.Create(StrConnectionFailed);
FDesktop := CreateService('com.sun.star.frame.Desktop');
FDispatchHelper := CreateService('com.sun.star.frame.DispatchHelper');
FIntrospection := CreateService('com.sun.star.beans.Introspection');
FReflection := CreateService('com.sun.star.reflection.CoreReflection');
end;
and:
class function TOpenOffice.CreateService(const ServiceName: string): Variant;
begin
Result := FServiceManager.createInstance(ServiceName);
if VarIsNull(Result) then
raise EOpenOfficeException.CreateFmt(StrCouldNotCreateService,
[ServiceName]);
end;

How do I add the key binding Shift+Ctrl+H X to the Delphi IDE using the ToolsApi?

Adding a new ShortCut to the Delphi IDE is not too difficult because the Open Tools API provides a service for this. I am trying something apparently more complex: Add a Wordstar like additional ShortCut:
I want something to happen when the user presses
Shift+Ctrl+H followed by the single key X
where X should work regardless of the state of the Shift key.
This is my code:
procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
GExpertsShortcut: Byte;
ShiftState: TShiftState;
FirstShortCut: TShortCut;
SecondShortCut: TShortCut;
begin
GExpertsShortcut := Ord('H');
ShiftState := [ssShift, ssCtrl];
FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
SecondShortCut := ShortCut(Ord('X'), []);
BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
end;
So, if I set ShiftState := [ssCtrl] pressing
Ctrl+H X
calls my TwoKeyBindingHandler method.
But with ShiftState := [ssShift, ssCtrl] pressing
Shift+Ctrl+H X
does nothing.
Oddly enough, when specifying ShiftState := [ssShift, ssCtrl] (which should only affect the first key) pressing
Shift+Ctrl+H Shift+X
calls my TwoKeyBindingHandler method, even though the second ShortCut is added without a modifier key.
Any idea? Is this maybe a known limitation/bug of the Delphi IDE/Open Tools API? Is there a known workaround?
I tried it in Delphi 2007 and Delphi 10 Seattle, no difference.
You should be able to do it using the GetKeyState function.
The program has two operations - Think of it as opening a drop down menu item. When ctr-shift-h is pressed your programme will need to flag that the 'Menu' is now open and that subsequent keypresses will either activate an option or close the 'menu' if an invalid key is presses.
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
procedure Form1.OnkeyDown(...)
begin
if Not H_MenuOpen then
if IsKeyDown(vk_Control) and IskeyDown(vk_Shift) and IsKeyDown(vk_H) then
begin
//Some Boolean in the form
H_MenuOpen:=True;
//Will probably need to invalidate some parameters here so that
//no control tries to process the key
exit;
end;
if H_MenuOpen then
begin
if key=vk_X then
begin
//x has been pressed
*Your code here*
//possibly invalidate some of the params again
exit;
end;
//Nothing valid
H_MenuOpen:=False;
end;
end;
OK, since apparently nobody has found an answer, here is what I ended up doing:
I had already planned to show a hint window listing all possible characters for the second key (actually that code was already working fine, using the approach suggested by Helen Fairgrieve in her answer to this question). Instead, I now register only a one-key shortcut:
BindingServices.AddKeyBinding([FirstShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
And in the TwoKeyBindingHandler method, I show a popup menu which contains those characters as the shortcuts. The IDE/VCL/Windows then handles the rest for me.
This is what it looks like:
It's not an answer to the actual question but it solves my problem. Sorry if you got here expecting something more.

Is there a version of Async Pro which works with Delphi XE3?

I want to use Async Pro in my Delphi XE3. I found a version A407 on SourceForge, which seems to be the latest. When I try to install the runtime package A407_R100.bpl I get an error that a data length is longer than 2GB. When I fix this (with some guesswork) I get 4 other errors. I can try to fix those as well, but I'm afraid I will have to patch so much of the code that it won't work anymore.
Is there a version of Async Pro which works with XE3? Or at least clear and proven instructions how to patch the code?
update
Here I found an AsyncPro library which seems to be more up-to-date; at least the packages are named A407_*140.bpl instead of A407_*100.bpl. I still had a couple of errors in this part of the code in AwAbsPd.pas:
procedure InitializeUnit;
var
TmpDateSeparator : char;
TmpDateFormat : string[15];
TmpDateTime : TDateTime;
begin
{Set Unix days base}
TmpDateFormat := ShortDateFormat;
TmpDateSeparator := DateSeparator;
DateSeparator := '/';
ShortDateFormat := 'mm/dd/yyyy';
TmpDateTime := StrToDateTime('01/01/1970');
UnixDaysBase := Trunc(TmpDateTime);
DateSeparator := TmpDateSeparator;
ShortDateFormat := TmpDateFormat;
Although SysUtils is in the "uses" clause I got errors that ShortDateFormat and DateSeparator weren't defined. So I hard-coded them:
procedure InitializeUnit;
var
TmpDateSeparator : char;
TmpDateFormat : string[15];
TmpDateTime : TDateTime;
// added stevenvh
var
DateSeparator: char;
ShortDateFormat: String;
ShortTimeFormat: String;
// end addition
begin
// added stevenvh
DateSeparator := '-';
ShortDateFormat := 'yyyy-mm-dd';
ShortTimeFormat := 'HH:mm:ss';
// end addition
{Set Unix days base}
TmpDateFormat := ShortDateFormat;
TmpDateSeparator := DateSeparator;
DateSeparator := '/';
ShortDateFormat := 'mm/dd/yyyy';
TmpDateTime := StrToDateTime('01/01/1970');
UnixDaysBase := Trunc(TmpDateTime);
DateSeparator := TmpDateSeparator;
ShortDateFormat := TmpDateFormat;
Nearly there! Both runtime and designtime packages compile, but when I try to install the designtime package I get an error that "01/01/1970" is not a valid date. This is not an error in the above code, because it remains the same "01/01/1970" when I change the date in the code.
Turns out there is only 1 other file which includes "01/01/1970" as text, but this is a .ocx file, so I'm not sure how or even if I should patch this.
Acording to this blog post:
http://blog.kassebaum.eu/?p=379
Async Proffesional is currently maintained by Roman Kassebaum but only for latest versions of RAD studio (both Delphi and CBuilder).
The menioned blog links to next source forge page:
http://sourceforge.net/projects/turbopowerasyncprofessionalnew/?source=navbar
Infromation on the page indicates that the project is closed and that it moved to github but no link is provided.
After doing some searching on GitHub I found the projects page
https://github.com/TurboPack/AsyncPro
Anyway since Roman Kassebaum is maintaing the project to be compatible with newest Delphi version it might not work for you.
So I strongly recomend you get in contact with Roman Kassebaum as he will best know which version should you use with your Delphi XE3 instalation or what needs to be fixed to make it compatible.
The official AsyncPro version moved to GitHub. You can find it under TurboPack. It supports the latest Delphi and C++Builder version.
I also created a branch for XE3. You can find it under TurboPack XE3.
Use FormatSettings.ShortDateFormat, FormatSettings.DateSeparator, ... instead of introducing your own variables. That would be closest to the original.
A cleaner approach would be using the date/time functions with a formatsettings overload instead of temporarily changing the global formatsettings.

How to detect system language in delphi for multi-language project?

I need to translate a program in others languages, actually I have the same program in 3 languages (english, spanish, portuguese), but I translated, recompiled, and I have 3 separate executables. And add more languages, and keep links, and adding new functions is driving me crazy.
So now I decided to keep a single executable, and a external language file, so adding new languages does not need recompiling, just editing the language file with a text editor, and everything is ok.
I want to keep all languages in a single external file. like international.lang
[portuguese]
greeting="Bem-vindo"
[spanish]
greeting="Ben venido"
if the file international.lang is not there, or your language is not on the file, the program will launch in english by default, with no errors. Just like most multi-languages programas based on resources.
So the question is, how detect the Windows language in delphi?
Any thoughts on my approach?
There is any way to replace all captions on dialogs programaticly?
ps: I'm using delphi7, and I can't find any component that is free that is good.
You can use the GetSystemDefaultLCID function to get the locale identifier and then use the VerLanguageName function to resolve the language associated name. or use the GetLocaleInfo function
Check this sample
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils;
procedure Test_VerLanguageName;
var
wLang : LangID;
szLang: Array [0..254] of Char;
begin
wLang := GetSystemDefaultLCID;
VerLanguageName(wLang, szLang, SizeOf(szLang));
Writeln(szLang);
end;
procedure Test_GetLocaleInfo;
var
Buffer : PChar;
Size : integer;
begin
Size := GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_SENGLANGUAGE, nil, 0);
GetMem(Buffer, Size);
try
GetLocaleInfo (LOCALE_USER_DEFAULT, LOCALE_SENGLANGUAGE, Buffer, Size);
Writeln(Buffer);
finally
FreeMem(Buffer);
end;
end;
begin
try
Test_VerLanguageName;
Test_GetLocaleInfo;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Note : Starting with Windows Vista exists new functions to get the same locale information, check these functions GetLocaleInfoEx, GetUserDefaultLocaleName and GetSystemDefaultLocaleName
I have the same problem although I have to deal with only two languages: English (default) and Polish.
I tried all the solutions listed above and none of them was working. I was changing system setting, rebooting etc. and always receiving language English.
When switched to Polish everything was displayed in Polish, all Polish locales were set but my application was receiving English as the OS language. After many tries I came across with quite easy and reliable workaround (I do not call it solution) that is good if you have to deal with a small number of languages.
So the trick is to check in what language the language list is returned by TLanguages.
function GetLang: Integer; //lcid
const
lcidEnglish = $9;
lcidPolish = $415;
var Idx: Integer;
begin
Result := Languages.IndexOf(lcidPolish);
if (Result > 0) and
(Languages.Name[Result].StartsWith('Polski', True)) //'Polski'is the Polish name of the language
then Result := lcidPolish
else Result := lcidEnglish;
end;
You can do the same for your three languages.
Hope it helps.

OLE Automation: How do i copy text between Word documents without using the clipboard

While doing som Word automation from Delphi XE, I have two documents open simultaneously. I want to copy the contents of a given range of one document to another range in the other document. How can I do this?
Consider the following code:
procedure TForm1.ManipulateDocuments;
var
vDoc1,vDoc2 : TWordDocument;
vFilename : olevariant;
vRange1,vRange2 : Range;
begin
vDoc1 := TWordDocument.Create(nil);
vDoc2 := TWordDocument.Create(nil);
try
vFilename := 'c:\temp\test1.doc';
vDoc1.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vFilename := 'c:\temp\test2.doc';
vDoc2.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vRange1 := GetSourceRange(vDoc1);
vRange2 := GetDestinationRange(vDoc2);
vRange2.CONTENTS := vRange1.CONTENTS; //What should I substitute for CONTENTS?
finally
vDoc1.Free;
vDoc2.Free;
end;
end;
Is there something I could substitute for CONTENTS? I can't use text, since I want to copy formatting, bookmarks, field codes etc. Do I have to do it another way alltogether? Any suggestions?
I don't know a way for earlier versions of Word, but for newer versions (2007 and up) you can export a range from a document to a fragment file, and then import it from another document. If you want early binding, you might need to import the type library (msword.olb), I don't know if Delphi XE has it. Otherwise the code might look like this:
function GetTempFileName(Prefix: string): string;
begin
SetLength(Result, MAX_PATH);
GetTempPath(MAX_PATH, PChar(Result));
windows.GetTempFileName(PChar(Result), PChar(Prefix), 0, PChar(Result));
end;
procedure TForm2.Button1Click(Sender: TObject);
const
// wdFormatDocument = 0;
wdFormatRTF = $00000006;
var
WordApp : OleVariant;
fragment: string;
vDoc1, vDoc2: OleVariant;
vRange1, vRange2: OleVariant;
begin
try
WordApp := GetActiveOleObject('Word.Application');
except
WordApp := CreateOleObject('Word.Application');
end;
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 120); // the export range
fragment := GetTempFileName('frg');
vRange1.ExportFragment(fragment, wdFormatRTF);
try
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(15, 15); // where to import
vRange2.ImportFragment(fragment);
finally
DeleteFile(fragment);
end;
end;
With my test, 'document' format threw an error (something like not being able to insert XML formatting), hence usage of RTF format.
edit:
With earlier versions, it seems to be possible to insert a named selection from one document to a selection in another document. The result seems not to be perfect regarding formatting if one of the selections happens to be in the middle of some text. But otherwise it seems to be working good.
...
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 188); // the transfer range
vDoc1.Bookmarks.Add('TransferSection', vRange1); // arbitrary bookmark name
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(103, 104); // where to import the bookmark
vRange2.Select;
vDoc2.ActiveWindow.Selection.InsertFile(vDoc1.FullName, 'TransferSection');
vDoc1.Bookmarks.Item('TransferSection').Delete; // no need for the bookmark anymore
 
If you can use the Office Open XML-format (ie. the docx file format that was introduced in Word 2007), then you can do this without automation.
Word versions prior to 2007 must install a compatibility pack which will enable docx-files for Word 2003, 2002 and 2000.
The docx-file is actually a zip-file that contains several xml-files. Try to change the extension of a docx-file from .docx to .zip and open this file in eg. WinZip.
So... Unzip docx-file and grab the xml-part you need. As pure string or as a xml document. Then you can inject this xml-part into the other docx-file. You need to know where in the xml-structure to grab/insert the xml, though. This will depend on how well you know the document structure and how much editing the user is allowed to do in the document.
I don't know how Word will handle duplicate bookmark names etc with this approach.
It seems I found the canonical solution to this question while digged into similar problem. The FormattedText property of Range object is the exact what do you need. Just use:
vRange2.FormattedText := vRange1;
and the contents of vRange1 will be copied into vRange2. Also, this works too:
vRange2 := vRange1;
Though, the second statement doesn't copy the formatting.
Why not use the clipboard? If all the text is selected in vDoc1, then to copy this to the clipboard involves one simple call: vDoc1.copy. Similarly, copying the contents of the clipboard to the second document requires one simple call: vDoc2.paste. The clipboard buffer will hold all the formatting information.

Resources