Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

How to ease the multilingual input for your users
Vladimir Shevchenko, January 1, 2001
Let assume that you live anywhere around the globe and want to develop an application with multilanguage support. It does not matter where do you live: Russia, USA, Canada, Switzerland etc, because you will always get the same problems with localization of user interfaces. Nowadays, you have differ...
Let assume that you live anywhere around the globe and want to develop an application with multilanguage support. It does not matter where do you live: Russia, USA, Canada, Switzerland etc, because you will always get the same problems with localization of user interfaces. Nowadays, you have different strategies for proper and cost-effective localization. The best strategy (from my point of view) is INTL by Steven Black, you shouldn't miss it in any case. However, all localized applications have a lack of multilanguage input from keyboard. I've never seen the application that allows to user to concentrate on their inputs because they should always control the current keyboard layout before starting to stuff the keyboard. It leads to slow down of input speed and additional (and unnecessary) key pressing.

Let imagine that you have a form with 40 textboxes and 25 of them require input in Russian language and the rest of them require input in any other language (English, for example). In addition, these textboxes reside on the form in random order without grouping by language (yes, you can group them, but it is not always possible). Your user will do two things:

  • Always control what is the current layout
  • Switch to necessary layout
In the worst case, he will switch the keyboard layout 40 times. I think that this user will always complain about speed of input and convenience. He will ask you "Why I should switch layout if I need input only in English or Russian ?". The solution of this problem is to switch keyboard layout programmatically through Win32 API calls.

Windows API has a group of functions related to keyboard stuffs. Everything you might want to do with your keyboard can be found in this group, so, it is logical to explore the power of API to solve our problem. The necessary functions are ActivateKeyboardLayout, GetKeyboardLayout, GetKeyboardLayoutName. After researching these functions I have decided to write a separate class instead of bury API calls in GUI object. At least, it is more OOP way to go and a better maintainability. Listing 1 shows the code for LanguageSwitcher class.

Listing 1. LanguageSwitcher class code

**************************************************
*-- Class:        languageswitcher (c:\projects\controls\lngswth\lngswth.vcx)
*-- ParentClass:  label
*-- BaseClass:    label
*
DEFINE CLASS languageswitcher AS label


	FontBold = .F.
	FontItalic = .F.
	FontSize = 7
	Alignment = 2
	Caption = "Language Switcher"
	Enabled = .T.
	Height = 12
	Visible = .F.
	Width = 92
	BackColor = RGB(255,255,0)
	Name = "languageswitcher"

	*-- Protected -- Holds the handles to installed layout handles with their
        * abbreviated names.
	PROTECTED akbdhandles[1,1]

	*-- Protected -- Holds info about known layouts
	PROTECTED alayout[1]


	*-- Protected -- Holds DLL's declaration
	PROTECTED PROCEDURE declaredll
		* Program....: LANGUAGESWITCHER.DECLAREDLL
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
		* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights
                *              Reserved.
		* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
		* Abstract...: Declares all necessary API calls
		* Input......: None
		* Output.....: None
		* Changes....:

		declare integer ActivateKeyboardLayout in Win32API integer, integer
		declare integer GetKeyboardLayout in Win32API long
		declare integer GetKeyboardLayoutList in Win32API integer, string @
		declare GetKeyboardLayoutName in Win32API string @
	ENDPROC


	*-- Protected -- Retrieves handles to all installed keyboard layouts
	PROTECTED PROCEDURE retrieveinstalledlayouthandles
		* Program....: LANGUAGESWITCHER.RETRIEVEINSTALLEDLAYOUTHANDLES
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
		* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights
                *              Reserved.
		* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
		* Abstract...: Retrieve all installed keyboard layout's handles and
                *              store them
		* Input......: None
		* Output.....: Fills class property aKBDHandles with handle values
		* Changes....:

		local lnLayoutCount, lcbuffer, lnBufLen, ix, iPos, iPos1,;
                 lnSavedLayout,lcoldEXACT


		********************************************************************
		** VS ** 21.04.98 Save SET Exact setting and turn it off
		********************************************************************
		lcoldExact = SET("EXACT")
		SET EXACT OFF

		********************************************************************
		** VS ** 20.04.98 Retrieve list of installed layouts, so we have to
                *                 prepare buffer
		*                 for receiving info about layout's handles
		********************************************************************
		lcBuffer = space(100)

		********************************************************************
		** VS ** 20.04.98 because returned values are DWORD otherwise VFP
                *                 will bombs out 
		********************************************************************
		lnBuflen = len(lcBuffer)/4  && 
		lnLayoutCount = GetKeyboardLayoutList(lnBuflen,@lcBuffer)

		********************************************************************
		** VS ** 21.04.98 Save current layout handle
		********************************************************************
		lnSavedLayout = Getkeyboardlayout(0)

		********************************************************************
		** VS ** 20.04.98 Now, parse the list by 4 bytes (DWORD) and fill an
                *                 array aKBDHandles
		********************************************************************
		dimension this.aKBDHandles[lnLayoutCount,2]
		FOR ix = 0 to lnLayoutCount-1
		    iPos = (ix *4)+1
		    iPos1 = ix+1  
		********************************************************************
		** VS ** 20.04.98 Get the handle to the layout
		********************************************************************
		    this.aKBDHandles[iPos1,1] =;
                     this.ConvertDWORDtoDEC(substr(lcBuffer,iPos,4))
		********************************************************************
		** VS ** 20.04.98 Retrieve corresponding layout name
		********************************************************************
		    this.aKBDHandles[iPos1,2] = ;
                     this.RetrieveLayoutName(this.aKBDHandles[iPos1,1])
		ENDFOR &&* ix = 0 to lnLayoutCount-1

		********************************************************************
		** VS ** 21.04.98 Restore original layout
		********************************************************************
		=activatekeyboardlayout(lnSavedLayout,0)

		********************************************************************
		** VS ** 21.04.98 Restore EXACT setting
		********************************************************************
		SET exact &lcoldEXACT
	ENDPROC


	*-- Protected -- Converts DWORD values to Decimal values
	PROTECTED PROCEDURE convertdwordtodec
		* Program....: LANGUAGESWITCHER.CONVERTDWORDTODEC
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights 
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
		* Abstract...: Converts DWORD values to plain Decimal
		* Input......: tcSTRtoCNV -  DWORD value passed as string
		* Output.....: Decimal value or .NULL. if passed value is not DWORD
		* Changes....:

		lparameters tcSTRtoCNV
		if len(tcSTRtoCNV) # 4
		  return .null.
		else
		  return asc(substr(tcSTRtoCNV,2))*256+asc(substr(tcSTRtoCNV,1))+;
                   asc(substr(tcSTRtoCNV,4))*16777216+asc(substr(tcSTRtoCNV,3))*65536
		endif
	ENDPROC


	*-- Protected -- Fills an array with known values for layout's abbreviation
        *                and numbers.
	PROTECTED PROCEDURE fillknowkbdlayoutinfo
		* Program....: LANGUAGESWITCHER.FILLKNOWKBDLAYOUTINFO
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights 
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
* Abstract...: Fill an array with known values of keyboard layout inffo
* Input......: None
* Output.....: Filled array aLayout
		* Changes....:

	***************************************************************************
	** VS ** 20.04.98 Initially, Windows 95 and higher supports 33 primary
        *                 layout
	***************************************************************************
		dimension this.aLayout[33]
		this.aLayout[1] = [09 EN English]
		this.aLayout[2] = [1C SQ Africaans]
		this.aLayout[3] = [1D SV Sveden]
		this.aLayout[4] = [1A HR Croatia]
		this.aLayout[5] = [22 UK Ukraina]
		this.aLayout[6] = [0C FR France]
		this.aLayout[7] = [0B FI Finnland]
		this.aLayout[8] = [1F TR Turkey]
		this.aLayout[9] = [1A SR Serbia]
		this.aLayout[10] = [1B SK Slovakia]
		this.aLayout[11] = [24 SL Slovenia]
		this.aLayout[12] = [07 DE Germany]
		this.aLayout[13] = [14 NO Norway]
		this.aLayout[14] = [26 LV Latvia]
		this.aLayout[15] = [03 CA Catalan]
		this.aLayout[16] = [27 LT Litva]
		this.aLayout[17] = [18 RO Romania]
		this.aLayout[18] = [15 PL Poland]
		this.aLayout[19] = [16 PT Portugese]
		this.aLayout[20] = [10 IT Italia]
		this.aLayout[21] = [0F IS Islandia]
		this.aLayout[22] = [0A ES Spain]
		this.aLayout[23] = [09 BA Indonesian]
		this.aLayout[24] = [06 DA Dutch]
		this.aLayout[25] = [2D EU Bascks]
		this.aLayout[26] = [0E HU Hungary]
		this.aLayout[27] = [23 BE Belorussia]
		this.aLayout[28] = [13 NI Netherlands]
		this.aLayout[29] = [02 BG Bulgaria]
		this.aLayout[30] = [08 GR Greece]
		this.aLayout[31] = [05 CZ Czchehoslovakia]
		this.aLayout[32] = [19 RU Russia]
		this.aLayout[33] = [25 ET Estonia]
	ENDPROC


	*-- Protected -- Returns layout name which  corresponds to given handle
	PROTECTED PROCEDURE retrievelayoutname
		* Program....: LANGUAGESWITCHER.RETRIEVELAYOUTNAME
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights 
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
* Abstract...: Get the layout name
* Input......: tnKBDHandle - handle to a keyboard layout
		* Output.....: Corresponding description string from aLayout
                *              property
		* Changes....:

		lparameter tnKBDHandle
		local lcLayoutName, lcPrimeLayout,ix, lcBuffer

	***************************************************************************
	** VS ** 21.04.98 Get the layout name corresponding to passed handle
	***************************************************************************
		lcBuffer = space(50)
		=ActivateKeyboardlayout(tnKBDHandle,0)
		=Getkeyboardlayoutname(@lcbuffer)

	***************************************************************************
	** VS ** 21.04.98 Extract primary layout number and find info about layout
	*                 in aLayout propery
	***************************************************************************
		lcPrimeLayout = right(alltrim(lcBuffer),3)
	***************************************************************************
	** VS ** 21.04.98 Cut off the trailing CHR(0)
	***************************************************************************
		lcPrimeLayout = left(lcPrimeLayout,2)
		ix = ascan(this.aLayout,lcPrimeLayout)
	***************************************************************************
	** VS ** 27.04.98 Return layout abbreviated name from layout array
	***************************************************************************
		return iif(ix > 0,substr(this.aLayout[ix],4,2),[UNKNOWN])
	ENDPROC


	*-- Public -- Switches layout to given layout
	PROCEDURE switchlayout
		* Program....: LANGUAGESWITCHER.SWITCHLAYOUT
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 21, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
		* Abstract...: Switches layout to the given layout
		* Input......: tcLName  - abbreviated name of layout (like EN, RU
                *              etc)
		* Output.....: None
		* Changes....:

		lparameters tcLName

	***************************************************************************
	** VS ** 21.04.98 First, check if given layout is loaded then just activate
        *                 him
	***************************************************************************
		ix = ascan(this.aKBDHandles, alltrim(tcLname))
		if ix > 0


	***************************************************************************
	** VS ** 21.04.98 OK, switch layout
	***************************************************************************
		      =activatekeyboardlayout(this.aKBDHandles[ix/2,1],0)
        	endif      
	ENDPROC


	PROCEDURE Destroy
	***************************************************************************
	** VS ** 20.04.98 If Application object is presented, then clear dll there
	***************************************************************************
		if type([goApp.Name]) # [C]
		  clear dlls
		endif
	ENDPROC


	PROCEDURE Init
		* Program....: LANGUAGESWITCHER
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 20, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights 
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
* Abstract...: Switch the keyboard layouts on the fly. It is very
*              useful in international apps

		* Changes....:

	***************************************************************************
	*-* VS ** 20.04.98 If goApp object is not presented, then declare dlls
        *                  directly
	*             Usually, I place all declaration in application object's
        *             method
	***************************************************************************
		if type([goApp.Name]) # [C]
		   this.DeclareDLL()
		endif   

	***************************************************************************
** VS ** 20.04.98 Now move this object off the screen to avoid unnecessary
*                 *headache...
	***************************************************************************
		this.Left = -3000

	***************************************************************************
	** VS ** 20.04.98 Fill the arrary property aLayouts with all known values
	*                 of keyboard layouts.
	***************************************************************************
		this.FillKnowKBDLayoutInfo()

	***************************************************************************
	** VS ** 20.04.98 Next thing to do is to fill out array property with
	*                 values of installed keyboard layouts handles.
	***************************************************************************
		this.RetrieveInstalledLayoutHandles()
	ENDPROC


ENDDEFINE
*
*-- EndDefine: languageswitcher
**************************************************
Some additional notes about LanguageSwitcher

If you have a language that is not found in FillKnowKBDLayout method (for example, Chineese or Japan languages, sorry, I do not have East enabled Windows on my computer) you might want to add these languages to the list. Here is how to do this:

  1. Install all necessary keyboard layouts through Control panel
  2. Open the LanguageSwitcher class in the Class designer
  3. Change property aKBDHandles from PROTECTED to PUBLIC
  4. Run test.scx form (will be shown later)
  5. Open debugger and find the LanguagerSwitcher object
  6. Look through the aKBDHandles array to see corresponding handles values. Note: handles are retrieved in their installation order
  7. Change property aKBDHandles back to PROTECTED
  8. Add these values to FillKnowKBDLayoutInfo method and don't forget to redimension the aLayout array properly
  9. Please, send these numbers to me for adding to the list of known languages.
Also, pay attention to the fact that you should declare/undeclare your API calls in one place, so, I check if an Application object is present to decide whether declare API calls at startup or not.

Using LanguageSwitcher class

Now we should create GUI classes that will use newly developed class. You will need to create three classes that allows to enter information via keyboard: Textbox, Editbox and Combobox. In this article I will show how to subclass only the Textbox class because you can proceed with an other classes similarly. Listing 2 shows the code for Textbox class with new functionality.

Listing 2. SelfSwitchedTextbox class code

**************************************************
*-- Class:        selfswitchedtextbox (c:\projects\controls\lngswth\lngswth.vcx)
*-- ParentClass:  textbox
*-- BaseClass:    textbox
*
DEFINE CLASS selfswitchedtextbox AS textbox


	Height = 23
	Width = 100
	*-- PUBLIC -- indicates whether switch keyboard layout to desired
	lswitchlayout = .T.
	Name = "selfswitchedtextbox"

	*-- PUBLIC -- Language that mostly used for key in.
	cinputlanguage = .F.



	PROCEDURE GotFocus
		* Program....: SELFSWITCHEDTEXTBOX.GOTFOCUS
		* Version....: 1.0
		* Author.....: ** Vladimir Shevchenko **
		* Date.......: April 27, 1998
* Notice.....: Copyright (c) 1998 ** VSN Consulting **, All Rights 
* Reserved.
* Compiler...: Visual FoxPro 05.00.00.0415 for Windows 
* Abstract...: Switch keyboard layout to desired (if necessary)
		* Input......: NONE
		* Output.....: NONE
		* Changes....:
	***************************************************************************
	** VS ** 27.04.98 Check if we should switch layout on entry
	*                 Then, we should check if layout name is defined
	***************************************************************************
		if this.lSwitchLayout
		   if type([this.cInputLanguage]) = [C]
	


	***************************************************************************
	** VS ** 27.04.98 Alright, now we call Switcher object
	***************************************************************************
		 ThisForm.LanguageSwitcher.Switchlayout(upper(this.cInputLanguage))
		   endif
		endif   
	ENDPROC
ENDDEFINE
*
*-- EndDefine: selfswitchedtextbox
**************************************************
All you will need is to change cInputLanguage property to desired layout's abbreviated name (as it shown in taskbar like EN, RU etc). Full list of abbreviated names (for Windows 95 OSR2 Russian edition) is in FillKnowKBDLayoutInfo method. If you don't want to switch layout for particular textbox, just set lSwitchLayout property to .f. and it is gone forever. Run your form and watch the switching of layout indicator in taskbar. Voila, you got it! ;) In addition, instead of dropping LanguageSwitcher object to each form (or base form class) you might consider to instantiate this object under the dependency tree of your application object. For example, Codebook developers can add this class definition to their LoadChildren method of Application class. Therefore, instead of using Thisform keyword you can write something like this:
goApp.LanguageSwitcher.SwitchLayout(upper(this.cInputLanguage))
By this way, you will spend less memory and time, which are valuable things.

Conclusion

Multilanguage input can seriously degrade the performance of user's input and can damage any convenience with using your application, so, you should decide yourself about how to improve it. I do hope that this article helps you in your development and will ease life of your users. All codes are placed in Public Domain, however, please keep the Copyright notices AS IS. I have uploaded ZIP file that contains a class library with all necessary classes and a test form to fiddle.

Vladimir Shevchenko
Vladimir Shevchenko is an experienced independent software developer with 10 years of experience. Started to work with Foxpro since its DOS version thru all Windows version up to current release of VFP. Worked as employee at government institutions like Central bank of Russia and then started independent consulting for companies around the world in 1997. Developed many projects using various tools like frameworks, report generators, Active X components and have clear ability to translate user requirement specifications into software design and coding schedules. Talented in designing clear, concise and easy-to-understand user interfaces.
More articles from this author
Vladimir Shevchenko, January 1, 2001
One of my clients wants to have a smart grid class. Among other cool features this class should be able to remove the columns from the grid on the fly. Basically, the user will click the right button over the grid and choose from popup menu the Remove current column option. I said, "No problem, Sir"...