Gerd Altmann

Contents

Preface

The next release of kbmMW Enterprise Edition will expand on the already powerful I18N features, by adding 5 new features, global short name methods, language augmentation, decomposition, unit conversion and SVG based flags.

We have already looked at the phrases and context translation features in the previous blogposts I18N #1 and I18N #2. You will benefit from having read those before reading this blogpost.

Global short name methods

Methods like I18N.Translate(…) and I18N.Format(…) also had a short name variant called I18N._(…).
In the next version an even shorter named variant exists, namely the global _(…) method.

It can be disabled by defining KBMMW_I18N_NO_GLOBAL_UNDERSCORE_FUNCTION in kbmMWConfig.inc and recompile kbmMW.

Language augmentation

The idea behind language augmentation, is that you may have an application which have a base set of functionality, but which may change its functionality during the time of its use, for example by loading dynamic forms or scripts from a server on the fly.

The base application, before any dynamic forms or scripts has been received, should allow for being shown in various languages, but it may not know anything about the features of the optional dynamic forms or scripts, until those have actually been loaded.

For that reason it would make sense to be able to provide a rudimentary translation setup to the application at bootstrap, and be able to augment that translation with an additional specialized translation setup, which is loaded along with the dynamic forms/scripts, and only handle specific translation of those.

For this purpose, kbmMW’s I18N framework will support language augmentation.

In the future, kbmMW I18N can operate two translation sets of each language, a base set and an augmented set.

When a translation of a phrase is requested, kbmMW will attempt translation via the augmented language set first. If that does not exist or no translation is found, it will fall back to attempt translation via the base language set and otherwise follow the normal course of action shown in the previous blog posts.

The use is relatively simple. As always you load a translation file using the I18N.Load method. If you use it as you are used to do, you will basically load the base translation (bootstrap).

However the Load method now has an extra optional argument called AAugmented:boolean, which if set to true, will add the loaded translation file as an augmentation to the already loaded base translation for that or those languages that are loaded.

Similarly Save, Merge and Delete also takes an optional argument to indicate if you want to operate with the base or the augmented translation.

The format of the language files has not changed to support this feature. So any language file can in principle be used to augment another language file which acts as a base.

Is it compulsory to use this feature? No. In fact I would say, do not use it unless you really have a very dynamic application that can benefit from it. Most static applications will be translated fine using only a bootstrap/base translation file.

Decomposition

Usually one translates static phrases into other static phrases. Sometimes one translates phrases with additional context info (like a count) into other phrases which accounts for that context. In both cases one intercepts, in code, the production of a display string, before it has been formatted with numbers and such. Eg.

I18N.Format('This is a number: %d',[10])

The string part is static, and is used as our lookup phrase in the translation data, which results in a different phrase (typically with the %d embedded into it) being used for the actual formatting of the display string.

However some times you may be provided a composite string, which already contains preformatted numbers. It could be that you got it from a system which you can’t influence to make it I18N aware.

But we still want to be able to translate the string: ‘This is a number: 22’ even though the number is part of the string.

For that reason kbmMW’s I18N framework now contains a new feature called decomposition.

Basically it makes it possible to define a number of masks that can be used to decompose a string into a formattable string and a set of arguments.

Let us look at an example. In this case a string which contains a weight (in Danish):

Vægten er 10 kg 

We would like the system to be able to translate that to different languages, regarldess of what the actual weight is. In English it should read:

The weight is 10 kg

And obviously if the original string contained the value 20 instead of 10, the translation should adjust accordingly.

To make that happen, we will define a decomposition in our language file.

decompositions:
  "Name1"                             : "Vægten er %d kg"

What we have done is define a decompositions object on the same level as the phrases object.

We have named a decomposition mask Name1. We will use that name when we want to decompose the specific string “Vægten er 20 kg” or “Vægten er 10 kg” etc.

Usually we know, in our program, where that text can appear.

If there are variations of the masks that should be matched under the same name, you can define an array of masks:

decompositions:
   "Name1"                             - "Vægten er %d kg"
                                       - "Vægten den er %d kg"

Notice the variation of the Danish phrase. In both cases we just want that translated to a single matching translation.

In the phrases section, we will add the translation as we are used to:

phrases:
   "Vægten er %d kg"                   : "The weight is %d kg"
(and optionally)
   "Vægten den er %d kg"               : "The weigh really is %d kg"

In your code you will use the decomposition translation method called DecomposeTranslate:

writeln(I18N.DecomposeTranslate('Name1','Vægten er 22 kg'));

Which will come out (with the English language selected) as “The weight is 22 kg”

You can also use the short name method I18N._D(…) or the global method _D(…) (unless it has been disabled) to do a decompose translate.

Unit conversion

The above example, translating a text containing a weight in kg from Danish to English is not necessarily a complete translation. What if the weight should be expressed in English pounds (lb) or stones or some other unit?

The upcoming release of kbmMW also provides a solution for that as part of the I18N framework.

The language file can now include a units section, in which one can define all sorts of units, grouped in various groups indicating their conversion compatibility. Eg. it does (usually) not make sense to convert from Kg to Meters, so everything weight related would be placed under one group, and everything length/distance related would be placed under another group.

As unit conversions must be used across languages, they are defined in the root of the translation file at the same level as the languages statement. The following is a sample units definition, which supports distance, temperature and weight:

units:
  distance:
    m:
      alias: [meter, meters]
      base: 1
    km:
      alias: [kilometer, kilometre]
      base: 1000
    dm:
      alias: [decimeter, decimetre]
      base: 0.1
    cm:
      alias: [centimeter, centimetre]
      base: 0.01
    mm:
      alias: [millimeter, millimetre]
      base: 0.001

  temperature:
    C:
      alias: [centigrade, celcius]
      base: 1
    F:
      alias: [fahrenheit]
      to_base: (data-32)*(5/9)
      from_base: data/(5/9)+32
    K:
      alias: [kelvin]
      to_base: data-273.15
      from_base: data+273.15

  weight:
    kg:
      alias: [kilogram]
      base: 1
    dg:
      alias: [decigram]
      base: 0.1
    cg:
      alias: [centigram]
      base: 0.01
    mg:
      alias: [milligram]
      base: 0.001
    t:
      alias: [tonne, ton, tons, metric tonne, metric ton]
      base: 1000
    lt:
      alias: [long ton, imperial ton]
      base: 1016.047
    st:
      alias: [short ton]
      base: 907.18474
    lb:
      alias: [pound]
      base: 0.45359237
    stone:
      alias: [stones]
      base: 6.350
    qr:
      alias: [quarter, quartier, av]
      base: 12.7

  volume:
    L:
      alias: [liter,litre]
      base: 1
    dL:
      alias: [deciliter,decilitre]
      base: 0.1
    cL:
      alias: [centiliter,centilitre]
      base: 0.01
    mL:
      alias: [milliliter,millilitre,cm3,cubiccentimeter,cubiccm]
      base: 0.001
    imperial quart:
      alias: [imperial quarts]
      base: 0.87987699
    us quart:
      alias: [us quarts, u.s. quart, u.s. quarts]
      base: 1.056688
    imperial pint:
      alias: [imperial pints]
      base: 1.75975399
    us pint:
      alias: [us pints, u.s. pint, u.s. pints]
      base: 2.11337641
    imperial gal:
      alias: [imperial gallon, imperial gallons ]
      base: 0.21996925
    gal:
      alias: [gallon, gallons, us gallon, us gal, u.s. gallon, 
              u.s. gal, u.s. gallons]
      base: 0.2641720523581
    cubic foot:
      base: 0.0353146667
    cubic inches:
      alias: [cubic inch]
      base: 61.023744
    imperial fluid ounce:
      alias: [imperial fluid ounces]
      base: 35.19508
    us fluid ounce:
      alias: [us fluid ounces, u.s. fluid ounce, u.s. fluid ounces]
      base: 33.814023
 

As you can see, there are 3 groups, and under each group is a number of units. The units may have aliases (different names that is the same unit). Each unit also have either a base constant or alternatively a combination of to_base and from_base expression.

The base constant of 1 indicates the true base of the constant. Hence in this example we have defined the kg to be the base of everything weight related, since it has a constant of 1. There should always be a unit within a group of units, which has the base of 1.

Other units (like mg) will then define its base according to the base (kg). The base of 0.001 for the mg unit should be read as 1 mg is 0.001 kg.

In the temperature group, you will find examples of the from_base and to_base expressions.

from_base defines how to get to the particular unit from the base unit.

to_base defines how to get to the base unit from the particular unit.

As C (centigrade) is the base unit (having base=1), from_base for Kelvin contains the expression to convert from C to Kelvin (read it as Kelvin from base), and to_base how to convert from Kelvin to C (read it as Kelvin to base).

Check the blog post User defined functions and kbmMemSQL to see a list of usable functions.

To take advantage of the unit definitions, use the I18N.ConvertUnit(…) method, which can also be abbreviated as I18N._(…) and even the global _(…).

It takes 3 arguments: Original value (double), Original unit, Requested unit.

Eg.

value:=I18N.ConvertUnit(20,'C','F');

This will convert from Centigrade to Fahrenheit and return 68 as the result.

value:=I18N.ConvertUnit(30,'l','ml'); 

Will convert from litre to millilitre and return 30000.

If a conversion is not possible, for example because the units are unknown or in different unit groups, ConvertUnit will throw an exception. You can use TryConvertUnit instead if you want to attempt conversion and get a boolean result back telling if the conversion was successful or not.

Now.. unit conversion is really nice, but what would be nicer, is if your translated phrase would automatically be able to also do unit conversion on the fly.

Obviously such a feature has been added too. Let us look at a phrase from before:

phrases:
   "Vægten er %d kg"                   : "The weight is %d kg"

As can be seen, the English translation will also report the weight in kg. But what if we wanted English translations of this phrase to report it in English pounds (lb)? It is quite easy. Let us change the phrase a bit:

phrases:
   "Vægten er %d kg"                   : "The weight is %{%d/kg/lb} lb"

The translated phrase now specifies that we want it as an integer, but only after it has been converted from kg to lb.

As this conversion would usually result in a floating point value with some decimals, we might want to specify that by using %f instead of %d in the source phrase and the destination phrase. Both sides must use equal types.

phrases:
   "Vægten er %.0f kg"                   : "The weight is %{%.2f/kg/lb} lb"

In original language (in this case Danish) no decimals will be shown. But when converted to English pounds, 2 decimal places will be available.

SVG based flags

In previous versions, it has been possible to provide a path to a file (typically PNG or JPG) which contained a small or a large flag for each language. The provision, loading and display of the files is the responsibility of the developer.

In next release, it is also supported to provide flags in SVG format. The advantage of SVG for flags is that it is scalable without quality loss, and it at the same time takes up very little space compared to a PNG or a JPG file. Further, by embedding the SVG directly into the language file, no additional files are needed to be transferred to the application that needs I18N handling.

This is an example of a Danish SVG based flag. Usually only the small flag definition will be used as SVG defined flags are fully scalable.

      # SVG based Danish flag
      small:
        type: svg
        data: |
          <svg>
          <path fill="#c8102e" d="M0 0h640.1v480H0z"/>
          <path fill="#ffffff" d="M205.7 0h68.6v480h-68.6z"/>
          <path fill="#ffffff" d="M0 205.7h640.1v68.6H0z"/>
          </svg>

Now all that is needed is a SVG viewer in the application. Fortunately kbmFMX Pro in next release contains a quite good SVG viewer, and even a flag component that makes the flag wave in the wind.

If you like what you read, please spread the word. Feel free to reshare the post with your peers!

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.