ipm icon indicating copy to clipboard operation
ipm copied to clipboard

How to define localized strings?

Open mvhenderson opened this issue 2 years ago • 16 comments

I've figured out how to define localized error messages using LOC resource in module.xml

<Resource Name="My.Errors.LOC"/>

And supporting file src/My/Errors.xml

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26">
  <Document name="My.Errors.LOC">
    <MsgFile Language="en">
      <MsgDomain Domain="MyErr">
        <Message Id="CustomError">Value %1 is invalid</Message>
      </MsgDomain>
    </MsgFile>
  </Document>
</Export>

Which when loaded generates localize/My/MyErrrors.xml file in the working copy and My.Errors include routine (read only):

ROUTINE My.Errors [Type=INC]
#define MyErrCustomError "<MyErr>CustomError"

Which I can include and use as $$$ERROR($$$MyErrCustomError, 42)

Is there a ZPM way to do this for non-error strings such as form labels?

mvhenderson avatar Sep 15 '21 06:09 mvhenderson

Just curious, how, you would like to use it? With $$$Text, or some other way? those LOC files are loaded as my localization files and goes to ^IRIS.Msg

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26">
  <Document name="demo.LOC">
    <MsgFile Language="en">
      <MsgDomain Domain="myapp">
        <Message Id="Information">Some Information</Message>
        <Message Id="Description">Some Description</Message>
      </MsgDomain>
    </MsgFile>
  </Document>
</Export>

This LOC file will generate

^IRIS.Msg("myapp","en","Description")="Some Description"
^IRIS.Msg("myapp","en","Information")="Some Information"

And this already can be used in Application in some way.

daimor avatar Sep 15 '21 11:09 daimor

correct, using $$$Text — what does the module.xml look like to load that demo.LOC resource? Where in the working copy do I put the file?

On Sep 15, 2021, at 6:08 AM, Dmitry Maslennikov @.***> wrote:

 Just curious, how, you would like to use it? With $$$Text, or some other way? those LOC files are loaded as my localization files and goes to ^IRIS.Msg

Some Information Some Description This LOC file will generate

^IRIS.Msg("myapp","en","Description")="Some Description" ^IRIS.Msg("myapp","en","Information")="Some Information" And this already can be used in Application in some way.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android.

mvhenderson avatar Sep 15 '21 12:09 mvhenderson

I don't know so much about $$$Text, but, what I found is, that it always generates new info in ^IRIS.Msg

So, this usage off macros $$$Text("@Information@Some Information","myapp","en") will generate the same data ^IRIS.Msg("myapp","en","Description")="Some Description"

with ZPM, suppose, I'd like to support two languages, and I've created two files en.xml and ru.xml With the content similar to above. I placed both files in folder src/localize (localize is hardcoded for LOC files, which expected under the SourcesRoot)

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="loc-test.ZPM">
    <Module>
      <Name>loc-test</Name>
      <Version>1.0.0</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="en.loc"/>
      <Resource Name="ru.loc"/>
      <Resource Name="demo.mac"/>
    </Module>
  </Document>
</Export>

And after install I've got this

^IRIS.Msg("myapp","en","Description")="Some Description"
^IRIS.Msg("myapp","en","Information")="Some Information"
^IRIS.Msg("myapp","ru","Description")="Описание"
^IRIS.Msg("myapp","ru","Information")="Информация"

But, if I would have this piece of code in my demo.mac

 Write $$$Text("@Information@Info","myapp","en")

It will override localization from LOC files, and I found only one way, to get the correct localization, is to read it directly, something like this

 Write !,$$$GetAppMessage("en", "myapp", "Information", "Info")

arguments are: language, domain, id, default

And this will read the correct value, which was in LOC files

So, probably, the best way would be to just override $$$Text macro, to be it like $$$GetAppMessage

daimor avatar Sep 15 '21 12:09 daimor

and if we consider /src as a root for module.xml resources, what is the folder for loc.xml files? is it should be in /loc ?

On Wed, Sep 15, 2021 at 8:35 AM Dmitry Maslennikov @.***> wrote:

I don't know so much about $$$Text, but, what I found is, that it always generates new info in ^IRIS.Msg

So, this usage off macros @.***@Some Information","myapp","en") will generate the same data ^IRIS.Msg("myapp","en","Description")="Some Description"

with ZPM, suppose, I'd like to support two languages, and I've created two files en.xml and ru.xml With the content similar to above. I placed both files in folder src/localize (localize is hardcoded for LOC files, which expected under the SourcesRoot)

<Module>

  <Name>loc-test</Name>

  <Version>1.0.0</Version>

  <Packaging>module</Packaging>

  <SourcesRoot>src</SourcesRoot>

  <Resource Name="en.loc"/>

  <Resource Name="ru.loc"/>

  <Resource Name="demo.mac"/>

</Module>

And after install I've got this

^IRIS.Msg("myapp","en","Description")="Some Description"

^IRIS.Msg("myapp","en","Information")="Some Information"

^IRIS.Msg("myapp","ru","Description")="Описание"

^IRIS.Msg("myapp","ru","Information")="Информация"

But, if I would have this piece of code in my demo.mac

Write @.***@Info","myapp","en")

It will override localization from LOC files, and I found only one way, to get the correct localization, is to read it directly, something like this

Write !,$$$GetAppMessage("en", "myapp", "Information", "Info")

arguments are: language, domain, id, default

And this will read the correct value, which was in LOC files

So, probably, the best way would be to just override $$$Text macro, to be it like $$$GetAppMessage

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/intersystems-community/zpm/issues/258#issuecomment-919979198, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVHEP33PBFLSFSF5OSISDTUCCHINANCNFSM5EBWPCZQ .

evshvarov avatar Sep 15 '21 12:09 evshvarov

At the moment localize inside src folder if it's configured

daimor avatar Sep 15 '21 13:09 daimor

inside /localize folder right?

On Wed, Sep 15, 2021 at 9:01 AM Dmitry Maslennikov @.***> wrote:

At the moment localize inside src folder if it's configured

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/intersystems-community/zpm/issues/258#issuecomment-919997143, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVHEP3HWYANGTWH7FFTIGTUCCKJPANCNFSM5EBWPCZQ .

evshvarov avatar Sep 15 '21 13:09 evshvarov

e.g. /src/localize/en.xml

On Wed, Sep 15, 2021 at 9:06 AM Евгений Шваров @.***> wrote:

inside /localize folder right?

On Wed, Sep 15, 2021 at 9:01 AM Dmitry Maslennikov < @.***> wrote:

At the moment localize inside src folder if it's configured

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/intersystems-community/zpm/issues/258#issuecomment-919997143, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVHEP3HWYANGTWH7FFTIGTUCCKJPANCNFSM5EBWPCZQ .

evshvarov avatar Sep 15 '21 13:09 evshvarov

yes

daimor avatar Sep 15 '21 13:09 daimor

would be great to document it.

On Wed, Sep 15, 2021 at 9:07 AM Dmitry Maslennikov @.***> wrote:

yes

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/intersystems-community/zpm/issues/258#issuecomment-920002234, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVHEP2IAV6AQYUABN5FXMTUCCLBTANCNFSM5EBWPCZQ .

evshvarov avatar Sep 15 '21 13:09 evshvarov

When I add the src/localize/en.xml and corresponding <Resource Name="en.loc"/> to the module.xml ZPM fails with a class not found error for my original My.Errors.LOC

mvhenderson avatar Sep 15 '21 13:09 mvhenderson

Project structure image And module.xml

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="loc-test.ZPM">
    <Module>
      <Name>loc-test</Name>
      <Version>1.0.0</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="My.Errors.LOC"/>
      <Resource Name="en.loc"/>
      <Resource Name="ru.loc"/>
      <Resource Name="demo.mac"/>
    </Module>
  </Document>
</Export>

daimor avatar Sep 15 '21 14:09 daimor

Just to note, that this functionality is going from InterSystems realization just for HealthShare, and have not been used or reviewed since that. There are a few points here that look strange to me.

So, I would only look at this, as to how we can improve it, to make it useful for us. And any suggestions will be helpful Btw, my current module, after installing generate a few more XML files in localize folder at the root of the project, ignoring src. excerpt from my verbose log of installation

[loc-test]      Activate START
Exporting messages for 'My.Errors' domain(s) to /opt/repo/loc-test/localize/MyErrors.xml
Exporting messages for 'myapp' domain(s) to /opt/repo/loc-test/localize/en.xml

It's like, exporting localization from developer's. Domain name is not supposed to have dots, so, for My.Errors.LOC, it exports domain MyErrors to MyErrors.xml

So, I think as a first improvement, I would suggest do not use SourcesRoot for localization files and place them in the root and keep localize. But, not sure, what to do with export localization after compile

daimor avatar Sep 15 '21 14:09 daimor

Totally agree we need to pull this out of src, and also feel ZPM should never write to the working copy unless in developer mode. I also think it is important to leverage paradigms that already are in widespread use (e.g. $$$Text, %MessageDictionary).

Here is an idea for your consideration. First the module.xml

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="zpm-sample.ZPM">
    <Module>
      <Name>zpm-sample</Name>
      <Version>0.1.0+SNAPSHOT</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="MyApp.PKG"/>

      <!-- module message domain(s) for use with $$$Text -->
      <Domain Name="MyApp"/>

      <!-- module error domain(s) for use with $$$ERROR -->
      <Domain Name="MyAppErr" IsError="1"/>

      <!-- main language to use during compilation -->
      <Language Name="en" Default="1"/>

      <!-- other supported languages -->
      <Language Name="ru"/>
      <Language Name="fr"/>
    </Module>
  </Document>
</Export>

File structure:

    zpm-sample
      ├── lang
      │   ├── en.xml
      │   ├── fr.xml
      │   └── ru.xml
      ├── src
      │   └── MyApp
      │       └── Main.cls
      └── module.xml

So the language files default to <working copy>/lang/*.xml. We could provide a LanguageRoot setting and/or a Language/@Path override too. I feel lang is more appropriate here than localize. Each language file contains one or more domains:

    <?xml version="1.0" encoding="UTF-8"?>
    <Export generator="IRIS" version="26">
      <Document name="en.LOC">
        <MsgFile Language="en">

          <!-- messages -->
          <MsgDomain Domain="MyApp">
            <Message Id="Greeting">Hello</Message>
          </MsgDomain>

          <!-- errors -->
          <MsgDomain Domain="MyAppErr">
            <Message Id="InvalidScope">The scope '%1' is invalid.</Message>
          </MsgDomain>

        </MsgFile>
      </Document>
    </Export>

I'm not sure if we need the Export/Document wrapper, whatever is easiest to implement with existing tools is fine for a little extra boilerplate.

From a ZPM lifecycle point of view the import would set the domain default language ^IRIS.Msg(<domain>) to the default specified in the module.xml. Post compile if in developer mode that language file should be updated to pickup any changes for commit to source control. The module author would execute zpm "compile my-app" as needed (but at least prior to submitting a pull-request) to automatically update the language xml file.

The final piece is the error message dictionary. Here the developer manually maintains the error domain messages. The Domain/@IsError setting tells ZPM to generate an include file after the import phase to ensure successful compile. When developer updates the error domain, a zpm "import my-app" would make the any new error macros available. Alternatively the module author could omit the IsError flag and maintain the INC file(s) by hand - benefits of that approach would be keeping the INC under source control, having the ability of which errors to expose via macro, and backward compatibility with code bases prior to the ZPM lifecycle.

PS: Lots of good info here: https://community.intersystems.com/post/localization-cach%C3%A9-dbms

mvhenderson avatar Sep 16 '21 02:09 mvhenderson

A brief note on history here, the .LOC document type was intended to take some practices from HS dev around error message localization (having them in an XML file ingested during build processes) and bring the source into the DB as a first-class citizen via %Studio.AbstractDocument so that it could be edited alongside other code.

The Export/Document wrapper is just how XML export of a %Studio.AbstractDocument works (which is the same as XML export of anything else) - I believe this could just as easily be UDL (same as everything else), but haven't tried that before.

Export during the Activate phase was to support some of the build processes in HS but I would agree that this shouldn't happen unless specifically requested. (There's a resource processor class, LocalizationExport, that should be used to explicitly request this export if needed.)

I'm a fan of @mvhenderson 's ideas above.

timleavitt avatar Sep 16 '21 11:09 timleavitt

Note: loading the module with -dev creates an entry in ^Sources for the generated localize; don't think we'll need that so should be removed when refactoring to move localize out of src.

^Sources=""
^Sources("ZPM","*","NoFolders")=1
^Sources("ZPM","zpm-sample")=$c(0)_"/iris/"
^Sources("cls","MyApp.")=$c(0)_"/iris/src/"
^Sources("loc","en")=$c(0)_"/iris/src/localize/"

mvhenderson avatar Sep 17 '21 04:09 mvhenderson