Back to Home

Localization in DBMS Caché / InterSystems Blog

sub Caché · oop · csp · zen · nosql · localization · web development · intersystems cache · cache · dbms

Localization in DBMS Caché

    Suppose you wrote a program that displays “Hello, World!”, For example: The
      write "Hello, World!"

    application works, everything is fine.
    But time passes, your application develops, becomes popular, and now, you need to display this line in another language, and the number and composition of the required languages ​​is not known in advance.
    Under cat you will learn how the localization problem is solved in Caché.


    Short review


    The Caché DBMS provides a ready-made mechanism that simplifies the localization of strings in console programs, the interface in web applications, strings in JavaScipt files, error messages, etc.
    Note: This topic was discussed in passing in a previous article .

    Let's say there is a project with many classes, programs, web pages, js scripts, etc.
    The localization mechanism works as follows:
    1. even at the stage of compiling the project, all the lines to be localized are “fished out” and stored inside the database in a specific format.
    2. instead of the lines themselves, a certain code is substituted into the compiled code itself, which already at the execution stage will, depending on the current language of the session, issue one or another value from the store.

    The entire localization process is completely transparent to the programmer.
    The developer gets rid of the need to manually fill out a certain string store (a table in the database or a resource file), as well as writing code to manage this entire infrastructure, such as changing the language at runtime, exporting / importing data into various formats for the translator, etc. d.

    As a result, we have:
    1. readable - uncluttered by excess - source code;
    2. Autocomplete storage of localized strings
      Note: When you delete lines from code from the repository, they are not deleted. To clean the repository from such phantoms, it is easier to clean it and recompile the project.
    3. change the current language "on the fly." This applies to both web applications and regular programs;
    4. the ability to get a string in a given language, from a given domain (about domains just below);
    5. ready-made methods for export / import storage in XML.

    So, let's take a closer look at how this works, as well as all sorts of localization examples.

    Introduction


    Let's create a MAC program with the following content: Result:

    #Include %occMessages
    test() {  
      
      write "$$$DefaultLanguage=",$$$DefaultLanguage,!
      write "$$$SessionLanguage=",$$$SessionLanguage,!
      
      set msg1=$$$Text("Привет, Мир!","asd")
      set msg2=$$$Text("@my@Привет, Мир!","asd")
      write msg1,!,msg2,!

    }



    USER>d ^test
    $$$DefaultLanguage=ru
    $$$SessionLanguage=ru
    Привет, Мир!
    Привет, Мир!

    What did we get?

    First, the global appeared in the database
    ^CacheMsg("asd") = "ru"
    ^CacheMsg("asd","ru",2915927081) = "Привет, Мир!"
    ^CacheMsg("asd","ru","my") = "Привет, Мир!"

    Secondly, if you hover over the $$$ Text macro , you can see the code into which it is expanded.
    For the example above, the intermediate (expanded) program code (INT code) will be as follows: As for the example above , you should pay attention to the following things:

    test() {  
      write "$$$DefaultLanguage=",$get(^%SYS("LANGUAGE","CURRENT"),"en"),!
      write "$$$SessionLanguage=",$get(^||%Language,"en"),!
      set msg1=$get(^CacheMsg("asd",$get(^||%Language,"en"),"2915927081"),"Привет, Мир!")
      set msg2=$get(^CacheMsg("asd",$get(^||%Language,"en"),"my"),"Привет, Мир!")
      write msg1,!,msg2,!
    }



    1. lines in the program should be written initially in the language that is registered by default in the Caché DBMS in the current locale (configured in the Management Portal);
      Note: When using string identifiers instead of their hash, this is not so important.
    2. for each line, the macro calculates its CRC32, and all data — CRC32 or the line identifier, domain, current system language — is stored in the ^ CacheMsg global ;
    3. instead of the string itself, a code is inserted that takes into account the value in the private global ^ ||% Language ;
    4. if the user requests a string in a language for which there is no translation (there is no data in the repository), the original string will be returned;
    5. the domain mechanism allows you to logically separate localized strings, for example, different translations for the same strings, etc.

    If for some reason you are not satisfied with the current $$$ Text macro algorithm , for example, you want to set the default language differently or store the data in another place or ..., you can create your analogue.
    And the macros ## Expression and / or ## Function will help you with this .

    Let's continue our example.
    Let's add a new language. To do this, you need to unload the string store into a file and give it to the translators, then load the translation back, but with a different language.
    Data can be downloaded in various ways and in different formats.
    We will use the standard methods of the % MessageDictionary class : Import (), ImportDir (),Export (), ExportDomainList ():
      do ##class(%MessageDictionary).Export("messages.xml","ru")

    In the directory of our database we get the file " messages_ru.xml ". Rename it to " messages_en.xml ", change the language in it to " en " and translate the contents.
    Next, import it back into our repository:
      do ##class(%MessageDictionary).Import("messages_en.xml")

    Global will take the following form:
    ^CacheMsg("asd") = "ru"
    ^CacheMsg("asd","en",2915927081) = "Hello, World!"
    ^CacheMsg("asd","en","my") = "Hello, World!"
    ^CacheMsg("asd","ru",2915927081) = "Привет, Мир!"
    ^CacheMsg("asd","ru","my") = "Привет, Мир!"

    Now we can change the language "on the fly", for example: Result:

    #Include %occMessages

    test()
    {  

      set $$$SessionLanguageNode="ru"

      set msg1=$$$Text("Привет, Мир!","asd")
      set msg2=$$$Text("@my@Привет, Мир!","asd")
      write msg1,!,msg2,!

      set $$$SessionLanguageNode="en"

      set msg1=$$$Text("Привет, Мир!","asd")
      set msg2=$$$Text("@my@Привет, Мир!","asd")
      write msg1,!,msg2,!
      
      set $$$SessionLanguageNode="pt-br"

      set msg1=$$$Text("Привет, Мир!","asd")
      set msg2=$$$Text("@my@Привет, Мир!","asd")
      write msg1,!,msg2,!

    }



    USER>d ^test
    Привет, Мир!
    Привет, Мир!
    Hello, World!
    Hello, World!
    Привет, Мир!
    Привет, Мир!

    Pay attention to the last option.

    Non- web application localization example (regular class)


    Localization of class methods:

    Include %occErrors

    Class demo.test Extends %Persistent
    {

    Parameter DOMAIN = "asd";

    ClassMethod Test()
    {
      do ##class(%MessageDictionary).SetSessionLanguage("ru")

      write $$$Text("Привет, Мир!"),!

      do ##class(%MessageDictionary).SetSessionLanguage("en")

      write $$$Text("Привет, Мир!"),!

      do ##class(%MessageDictionary).SetSessionLanguage("pt-br")

      write $$$Text("Привет, Мир!"),!
      
      #dim ex as %Exception.AbstractException
      
      try
      {

        $$$ThrowStatus($$$ERR($$$AccessDenied))

      }catch (ex)
      {
        write $system.Status.GetErrorText(ex.AsStatus(),"ru"),!
        write $system.Status.GetErrorText(ex.AsStatus(),"en"),!
        write $system.Status.GetErrorText(ex.AsStatus(),"pt-br"),!
      }
    }

    }
    Note: you can, of course, use the macros described above.
    Result:
    USER>d ##class(demo.test).Test()
    Привет, Мир!
    Hello, World!
    Привет, Мир!
    ОШИБКА #822: Отказано в доступе
    ERROR #822: Access Denied
    ERRO #822: Acesso Negado

    Pay attention to the following points:
    • messages for exceptions have already been translated into several languages. Since these are system messages, the data for them is stored in the % qCacheMsg system global ;
    • we set the domain name once, because by default the $$$ Text macro is designed for use in classes;
    • the macro $$$ Text, although designed for use in web applications, is nevertheless quite suitable for offline environments.

    Web Application Localization Example


    Consider the following example:

    /// Created using the page template: Default
    Class demo.test Extends %ZEN.Component.page
    {

    /// Имя приложения, которому принадлежит эта страница.
    Parameter APPLICATION;

    /// Отображаемое имя для нового приложения.
    Parameter PAGENAME;

    /// Домен, используемый для локализации.
    Parameter DOMAIN = "asd";

    /// Этот блок Style содержит определение CSS стиля страницы.
    XData Style
    {

    }

    /// Этот XML блок описывает содержимое этой страницы.
    XData Contents [ XMLNamespace = "www.intersystems.com/zen" ]
    {

      
      

    Read Next