Developing Multilingual Solution with FileMaker

I come across a client who needs a solution interface in two languages (English & German). I was wondering what approach to follow so that he gets what he needs without adding so much of work (or maintenance later on).

These are the approaches came to my mind for now-

1. Place labels for both the languages. "eg.- First Name/ Vorname" (The easiest one I think, but might give problems in certain parts where layout space is limited)

2. Creating a Table with Fields to store the labels and then placing these Fields in place of labels (having two records for labels one for English and one for German). And then define required relationships so correct labels are shown when user selects UI language from a utility layout.

3. Having two versions of all the layouts, one with English labels and other with German labels. (Please don't kill me for this . I know BIG NO to go this way, but just listed).

I am curious to know if anyone have done this before and how? Please share any ideas or your prior experience with this kind of requirement.

The above search was for "multiligual" within the last year. I clicked on the magnifying glass in the upper right and when the short list appeared, I clicked to show the full list to limit within the last year. No surprise, this thread is also in the list!

1- bigtom pointed out that there is more to localization than the interface labels (I prefer to speak about UI because the same variables can be used for dialog boxes, custom menus, etc. and not just labels). You will need to think about value lists, but also about error codes, especially if you use app specific error codes. Another degree of complexity is added when data entered by the users needs to be in multiple languages. For example, in a multi language solution that deals with Products, you might need to have product names available in different languages (but they have to be entered by the users). I had the case in a solution I built for a customer who uses french as interface language, but had to print invoices to both French and English speaking customers.

2- Sometimes performance can suffer from techniques used for localization. For example: in the file referenced by bigtom, there is an SQL call in the calculation that produces the global variables that hold the localized values. This script will have to execute at each startup along with any other script you need to set up the environment. From a performance point of view, you add waiting time for the user while starting up, especially on WAN. I found that this time can be greatly reduced by caching the key/value pairs for each language I support. I do this by using a special table named Cache that stores all localization data (labels, error codes, language names, etc. - One record per data type per language). At startup, you load the content of 1 record for each localization type and parse it. In a solution with around 900 labels, caching helped me cut variable load time from around 1000 ms to a mere 130 ms on WAN. Once you finished development, localization data for UI is static data, so caching is a good option performance-wise (You still have the Localization table but you don't load data from it. When you change data in the Localization table, you just need to update the cache before putting the file back in production).

3- If you plan to use your global variables with JSON, say for caching if you go this route, NEVER use dots (.) in the variable names (dots separate levels in a JSON key). I recently tried to bring a pre-v16 solution to v16 and standardize the caching approach to use JSON but couldn't because in my naming conventions I used to use dots as word separators for global variables.

4- Maybe I will have some disagreement on this one, but I recommend to have the singulars and plurals as separate key/value pairs and not just add an "S" by concatenation. There are a few reasons for that, but the main one is because of invariable words. A word can be variable in a language and invariable in another (for example, "news" is invariable in english but variable in french).

5- You better have a high level of consistency in you variable naming. You don't want to go back to your localization table every time you want to find a variable name. You want to be able to guess the variable name without doing that, especially in large solutions. I recommend writing the naming conventions in your development documentations so you can go back and read them when you restart the work on an older file. I usually use plain english for my variable names, BUT I always enforce the naming conventions I adopted, like this: $$UI.NAME.OF.THE.VARIABLE (post v16, I will be using $$UI_NAME_OF_THE_VARIABLE instead).

6- In all cases and especially if you want to have the functionality that the customer can enter localized values for some data fields (my Product name example above), plan for having a default value that will kick in if the localized value is not entered. If the default language is english for example, plan for the english value of the field to be mandatory and the default. This way, in case the user forgot to input the french value, at least the invoice will be printed with an english name for the product and not a blank.

7- Almost forgot this one: if you have quotes " in the value of the UI variable, you need to escape them. I use an auto-enter calculation in the "value" field to do this without having to worry about escaping quotes (and missing some):

I would like to point out that the basic method I use in the demo file is the fastest and most balanced label localization method I have tried in the past few years and that is even over WAN. Although I am curious about the caching method. Do you have an example of that?

I Advise against using native JSON for anything you need to be absolutely fast. The native JSON functions get very slow the larger the JSON gets.

I agree about plurals.

The other thing you will want to look at is the use of as many icons as possible instead of text labels. It is the UI and not just labels, but need to start somewhere. I did one UI that had icons as value lists.

I totally agree. The method you use is really fast and compared to other methods the most robust I have found this far. It is the one I use too.

In my case, it was not the load time of UI variables that prompted to look at caching as an additional method to reduce load time at startup. My startup script on a particular solution did a bunch of things that amounted to load times of 5 to 10 seconds on WAN. But now, with caching I am bellow 2 seconds (sometimes over, but it is related to hardware and network latency).

Basically, I added a table (see screen captures) called Cached to my System file with 5 fields (only 3 are relevant to understanding: "Type", "languageAsNumber" & "data" ). Once I am ready for deployment, I run a script on the Localization tables and assemble the data "Ready to use" for the Startup script. A record is created for each Type of Data (Interface, Error Code, etc.) and for each language.

At startup, the script gets only one record for each Type of Data and parse it. For example, the script will have this calculation to get all the UI variables: Evaluate ( "Let ( [" & $uiDataWeGotFromCache & "] ; true )" ).

I added 2 screen captures to illustrate what I explained. The first one shows the "Interface" Table that contains the variable name and the default value and a portal to the "InterfaceLozalized" table that contains the language-specific values for each variable.The button "Update Cache" executes the script that packages the data and creates the Cache records.

The second screen capture shows the Cache record for "Interface" type of data in french (languageAsNumber = 2).

The startup script loads only one record (the Cache record) and does not go through the "Interface" table records. It saves a wee bit of time but done for all the "static" data loaded at startup it increased the overall performance of my startup script.

Thanks for the tip on JSON use for large sets. I will keep that in mind.

Take care.

Attachments

I did this using Button Bars loaded with calculated ExecuteSQL values, along the lines of "SELECT label_translation from Translations WHERE Table = Get (LayoutTableName), field = "name_of_field", language = "$$CurrentLanguage etc. (Sorry, don't have the file to hand). Any language can be supported with no further work other than loading a few hundred records for field names and the required translations. Seems to work pretty well. Logically you have to include layout names, button names and popover names as well as label names. Seems to work pretty well. Email me on nick@transmedia.co.uk if you want to discuss. I'm not saying this is the best method but I found it easy enough after getting the syntax right because it works purely on calculation code rather than scripting to load umpteen global variables with data.

For WAN, I never transfer the entire language table to the device. I keep a preset tied to the account name and pull in the default based on that at startup. If they want to see another language I get the calc to evaluate via PSOS for only the language needed. It is a lot less data over WAN that way.

nick@transmedia.co.uk I think it is more effective to pay the SQL tax only once. My calculation can be used in any evaluated calc...Button Bar, HideObject, anywhere. In fact, the last time I posted this file it was tied to a Hide calc on a single object and the only script needed was Refresh Window or Refresh Object after a language selection.

nick@transmedia.co.uk You mean an example where the calc is not in a script? I already posted an example with a script.

You really need to set the language and refresh the text objects so you need at least one script and it is best to keep what is happening visible and easy to find. Hiding it away helps no one, but if you want to see it that way I can do it for you.

This is something that I did years ago when I need a multi lingual solution.

I created button name in the majority language and then used a system of tool-tip messages to do the translations after the the user selected a language. Later I added this choice to the startup script at log-on based on user preference.

I had over a hundred buttons and the thing that made it work fast and well was the creation of a data array of button names in a custom function. Because there was no reference to a data table where it had to read a record, etc., it turned out to be very, very fast. this was using FM 11, so I am sure that it could be better today. custom functions ad data arrays can do some very cool things.