Multi-language Access

JohnPapa

Registered User.
Local time
Today, 15:07
Joined
Aug 15, 2010
Messages
1,117
My application (www.VisualDentist.com) is multi-language and recently I've had a request to translate the application to German. I want to make it as easy as possible for the person who is doing the translation. All form and report labels are kept in a file "tblMultiLanguage" where English is field txt02 and German will be txt05.

I could painstakingly go through every form and include a control which when clicked executes
Code:
DoCmd.OpenForm "frmMultiLanguage", , , "txtFormName = '" & Me.Name & "'"
This will bring up only the specific labels for the current form and the translator could update txt05.

Can anyone think of a way that would avoid the introduction of a control in every form. If it would help nearly every form has a label called "lblMain", whose click event can be used.

For the reports I do not see a quick way around it.

Thanks
John
 
Thanks, I had a look at the link you provided.

In my case the table is in place. I was looking for an easy way(If possible) to call the specific table for every form, without having to enter a control on every form.
 
I'm not sure of what you have exactly in terms of Tables, forms, messages etc that you need for multi language support.
I worked on a fairly extensive application (Oracle/C) that had Eng/French. and allowed you to move from language to language.
We knew the Forms and controls and captions. There was a table set up to deal with the id ,name, and language of elements. Its been over 7 years since I last saw the code, but that's the gist of things.
Elements were Forms, Messages, Controls etc.

I'm not sure what the "simplest" method might be.

I do have a contact who has developed some commercial software products based on Access and the products are in international use, so language has had to be an issue he has addressed.

Can you tell me more about the issue or post a sample showing the approach/options you may be considering?
 
jdraw thanks for your input,

The software works as multi-language with 4 languages. I just want to replace one of the languages with German. As I mentioned the captions, labels of forms and reports are in a table (tblLanguage) whose structure is
Code:
lngID
txtReportName or txtFormName
txtControlName
txtLanguage01
txtLanguage02
txtLanguage03
txtLanguage04
When you open a form or report we lookup the control names and if we have selected Language01 we enter txtLanguage01 for the specific control label.

It is useful for the person doing the translation (in this case updating txtLanguage04) to be able to see the form and the context in which the table is used. I can create a control which when clicked, it would open frmLanguage (based on tblLanguage) with a filter on the specific txtFormName and this form would show all txtLanguage01 to 04. The person doing the tranlation can then update txtLanguage04.

The questin is whether you think of a way where I can call frmLanguage
without having to create a control on each form?

Thanks,
John
 
Can anyone think of a way that would avoid the introduction of a control in every form. If it would help nearly every form has a label called "lblMain", whose click event can be used.

Hi JohnPapa

From what I see with languages like Objective C / VB.NET and /C# the method of making software multi language is normally that you have a text file with keys. When your software starts it chooses the text file with the language of choice and then reads the language in. The programs generally used for this like xCode / Visual Studio do this all for you. Access does not.

Whilst my suggestion is a mouthful at the start it the easiest way to do this in access in my opinion. Plus you can than hand off a text file to a translator and they can do it without needing your assistance.

By the way having multiple forms for different languages is also done, in at least Objective C but I have not seen a need for it. I would think it generally would be done when you have size issues with your text.

So I use the old .ini file. I then have files for each language. eg languageDE.ini and languageEN.ini I suppose if you wanted to bring this up to 2013 you would use XML formatted files.

Then in each file I have keys like this..... note the key name is exactly the same.
EN file
myKeyNameChangeRecord=Make changes to this record

DE file
myKeyNameChangeRecord=Ändern dieses Datensatzes

Then generally I use the following APIs.

Code:
Public Declare Function GetPrivateProfileString _
    Lib "kernel32" Alias "GetPrivateProfileStringW" _
    (ByVal lpApplicationName As String, _
    ByVal lpKeyName As String, _
    ByVal lpDefault As String, _
    lpReturnedString As Any, _
    ByVal nSize As Long, _
    ByVal lpFileName As String) As Long
   
   
Public Declare Function WritePrivateProfileString _
   Lib "kernel32" Alias "WritePrivateProfileStringW" _
  (ByVal lpSectionName As String, _
   ByVal lpKeyName As String, _
   ByVal lpString As String, _
   ByVal lpFileName As String) As Long
NOTE the "W" on there which means I am have the ability to also code with Japanese and Chinese text. I cannot read Chinese :-) but I dont have to. I just hand off an english file with for exmaple "myKeyNameChangeRecord=Make changes to this record" in it. And it is returned with

"myKeyNameChangeRecord=chinese text is here"

The file name would be languageCN and as long as I point my code to this file it will show the chinese characters instead of the english ones.

Now in your code anywhere you just need to call it. I use a long function with optional parts you can cut it right down if you like.

In your text code you can have sections which look like this

[SectionName]

iKeyName = the key name
iDefValue = is a value if the key is not found
iInifile = path to your text file.

Code:
Public Function ProfileGetItem(Optional iSection As String, _
                                Optional iKeyName As String, _
                                Optional iDefValue As String, _
                                Optional iInifile As String) As String


Dim RetVal As Long
Dim cSize As Long
Dim Buf() As Byte

If Len(iSection) = 0 Then iSection = strlanguage
If Len(iDefValue) = 0 Then iDefValue = sDefValue
If Len(iInifile) = 0 Then iInifile = sInifile

'ReDim Buf(254)
'cSize = 255

ReDim Buf(800)
cSize = 800


RetVal = GetPrivateProfileString(StrConv(iSection, vbUnicode), _
                                    StrConv(iKeyName, vbUnicode), _
                                    StrConv(iDefValue, vbUnicode), _
                                    Buf(0), _
                                    cSize, _
                                    StrConv(iInifile, vbUnicode))

If RetVal > 0 Then
      ProfileGetItem = Left(Buf, RetVal)
End If

End Function
You could cut this right down to just the key if you like. If you get the other information from elsewhere or at start up.

Code:
Public Function textForKeyNamed(ByVal iKeyName As String) As String


Dim RetVal As Long
Dim cSize As Long
Dim Buf() As Byte
'THESE YOU NEED TO SET YOURSELF SOMEWHERE
 iSection = strlanguage
 iDefValue = sDefValue
iInifile = sInifile

'ReDim Buf(254)
'cSize = 255

ReDim Buf(800)
cSize = 800


RetVal = GetPrivateProfileString(StrConv(iSection, vbUnicode), _
                                    StrConv(iKeyName, vbUnicode), _
                                    StrConv(iDefValue, vbUnicode), _
                                    Buf(0), _
                                    cSize, _
                                    StrConv(iInifile, vbUnicode))

If RetVal > 0 Then
      ProfileGetItem = Left(Buf, RetVal)
End If

End Function
Now in your code you write it once in the open or load method of the form

Code:
Me.myButtonName.ControlTipText =  textForKeyNamed("myKeyName1")
Me.myControlName.Caption  =  textForKeyNamed("myKeyName2")

Dont forget to resize your labels etc. Languages like German are generally longer than english.
 
PS. If you really want to be good if the key is not found in the foreign language you can make a fall back to your English file as Generally it is fully filled out.
 
darbid many thanks for your reply,

I will have a closer look at the info you provide for future projects.

As I mentioned the Language table is in place and I have provided a form interface to this table (all labels) to the person doing the translation. As she fills in the German text the application labels turn to German.

Sometimes though, one needs the context in which the text is used. That is why I wanted to provide the extra feature where once in a specific form the person doing the translation could look at only the lables for the specific form.

I do not want to include a button in every form that when clicked you get the labels for the specific form.

The general question is: CAN A USER CALL FROM ANY FORM (frmX) ANOTHER FORM (frmY) WITHOUT INTRODUCING A CONTROL ON frmX WHICH WOULD NEED TO BE CLICKED TO CALL frmY?

John
 
Sometimes though, one needs the context in which the text is used. That is why I wanted to provide the extra feature where once in a specific form the person doing the translation could look at only the lables for the specific form.
That is also why the text files are used. In other languages you have a comment above the english text for example for the translator to understand the context. In your case you would use a column.

The general question is: CAN A USER CALL FROM ANY FORM (frmX) ANOTHER FORM (frmY) WITHOUT INTRODUCING A CONTROL ON frmX WHICH WOULD NEED TO BE CLICKED TO CALL frmY?

John

I am not sure I understand. The user is seeing frmX and there needs to be some call to frmY. This is possible as long as frmY exists ie is open. It does not have to be in focus or visible.
eg call a control
Code:
Forms![frmY].Controls("frmYControlName")
You can also call a method on another form
Code:
Call Forms("frmY").publicFormMethod("withOneSting")

You don't need a control to initiate this code but if there is no control you need something to get it started, a timer, event or something.
 
Many thanks darbid for your reply,

I am trying understand your suggestion
Code:
Call Forms("frmY").publicFormMethod("withOneSting")

Just to clarify frmY is based on tblLanguage whose strusture is

lngID txtFormName txtControlName txtLanguage01 02 03 04
123 frmX lblExample English German

So when the user opens frmY (while in frmX) he can see the English description (txtLanguage01) and can fill in online txtLanguage04, which is the German translation of txtLanguage01.

Can I define some public code in a module which would get executed in any form including frmX, by key combination (ex control-J), which inturn would open frmY (filtered for the labels found in frmX)?

Hope this helps,
John
 
Many thanks darbid for your reply,

I am trying understand your suggestion
Code:
Call Forms("frmY").publicFormMethod("withOneSting")

Just to clarify frmY is based on tblLanguage whose strusture is

Code:
lngID txtFormName txtControlName txtLanguage01      02      03      04
123       frmX       lblExample      English                     German

So when the user opens frmY (while in frmX) he can see the English description (txtLanguage01) and can fill in online txtLanguage04, which is the German translation of txtLanguage01.

Can I define some public code in a module which would get executed in any form including frmX, by key combination (ex control-J), which inturn would open frmY (filtered for the labels found in frmX)?

Hope this helps,
John
 
First I have to say I am having a hard time understanding, but it is most likely me.

I think I understand, you want to set up the ability for the translator to see the forms working so that they know in context what a label or button has on it and then translate it.

I dont know an easy way of doing this. My thought would be to make screen shots and just tell the translator to use them. The translator does not need to then understand the program or forms etc.

Otherwise if you are createing some "Translators" form where they are adding text to the for example German column directly, then you could have a dropdown box on this form with the other formnames. If the translator needs to view the form then they can choose it from the dropdown box and you can open it as per usual. This of course might lead to other problems if the form for example requires a special record set.

I am sorry if I am not helping you I am not sure we are on the same track at the moment.
 
I attach two picture files that should clear up matters. (Should have done this earlier!!)

In darbid1.jpg you can see frmDentalInfo. On the left (red dotted circle) there is a button which sticks out like a sore thumb. Its caption is "Language". When I click on this button I execute

Code:
DoCmd.OpenForm "frmLanguage", , , "txtFormName = '" & Me.Name & "'"

in this case Me.Name is "frmDentalInfo". In darbid2.jpg, you can see frmLanguage which opens over frmDentalInfo. You can see an English column and a German column. Take for example the caption "Renew Appointment". If I translate this to the German equivalent, then if I switch to German, the specific button will have this value. The same will apply for all the rest entries in the German column (which at present has entries the same as the English column)

Question: Is there a way that I can call frmLanguage for any form WITHOUT having to create a button such as the one with caption = "Language"?

Hope this helps,
John
 

Attachments

  • darbid1.jpg
    darbid1.jpg
    99.6 KB · Views: 159
  • darbid2.jpg
    darbid2.jpg
    84.1 KB · Views: 163
I see what you mean. But just to be sure I will put it in my own words.
1. Translator navigates to a form
2. Translator clicks a button in your case, button named language
3. The resulting form shows all the rows that need translating for this form and as the form is open can tell the context of the word.

This process you would like to make better or avoid, in particular doing away with the button on each form.

I am not sure which is best, maybe some of the other experts will chime in soon and give their opinion. As I see it there are a number of options;

1. Translator navigates to form.
2. Translator already has the multilanguage form open but it has a drop down combo with a list of all forms.
3. Translator just chooses the current form they are looking at and then the forms table filters to this record.

or

1. You make static screen shots
2. pdf them and then put your comments on each translation to assist the translator
3. give them the pdfs to look at while translating

or

1. you add an extra column to your table with a text explanation to give them context.

or

This is stretching it and making it overly complicated but you could have your German column and have the rows where no translation is present empty. This would mean that the form would now show with no text. Now you could create a function/ routine (I think) which takes your form name as the only parameter and then checks all controls on the form to see if the control "text" depending on if it is a button/label/tip etc has something. If it is empty then the translator gets a box with the English text and a field to type the German text. This is going to get complicated but if set up right would look pretty cool.

In this case I always think about re-use, you are always going to add a comment, button or something which will need to be sent back to the translator. So choose a way (even if you do put a button on each form) which always for re-use when the form changes.
 
Thanks Darbid,

At the end of the day , the question boils down the possibility of executing the following command (when in any form), without attaching the command to the event of say a button

Code:
DoCmd.OpenForm "frmLanguage", , , "txtFormName = '" & Me.Name & "'"

There is a workaround that I thought after reading your input. Every form executes the same setup code, which resides in a public sub. I could include the code above in the setup and control this code by setting a variable elsewhere. The variable would define whether the software is in translate mode or not. If it is, then the form will pop up automatically.

John
 
Yes. So use your open or load event as the event which initiates your code. Sounds good to me.
 

Users who are viewing this thread

Back
Top Bottom