Another note on this - there are some new behaviors in recent IRIS versions around the SameSite flag on cookies. (see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/Sam... for general background and https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls... for an explanation of the IRIS changes). This can make cookies behave differently in iframes, doing HTTP redirects, even opening via a link; how you get to a page has a bearing on cookie behavior.

Not sure if that fed into the issue you saw but it's worth noting.

Maybe not simpler, and definitely more complicated if you use a stream rather than a string with MAXLEN="" (which I'll demo below); here's what it ends up looking like:

Class DC.Demo.JSON Extends %String [ ClassType = datatype ]
{

Parameter MAXLEN;

/// Builds value array with subscripts set to values of properties in the JSON stream.
ClassMethod BuildValueArray(value As DC.Demo.JSON, ByRef valueArray As %String) As %Status [ Private ]
{
    set sc = $$$OK
    try {
        set object = {}.%FromJSON(value)
        do ..AddObjectToValueArray(object,.valueArray)
    } catch e {
        set sc = e.AsStatus()
    }
    quit sc
}

ClassMethod AddObjectToValueArray(object As %DynamicAbstractObject, ByRef valueArray, truncateToLength As %String = 255)
{
    set iter = object.%GetIterator()
    while iter.%GetNext(.key,.value) {
        if $isobject(value) {
            do ..AddObjectToValueArray(value,.valueArray)
        } else {
            set sub = $extract(value,1,truncateToLength)
            set valueArray(sub) = ""
        }
    }
}

}

Class DC.Demo.IndexJSON Extends %Persistent
{

Property JSON As JSON(MAXLEN = "");

Index JSONValues On JSON(KEYS);

ClassMethod Run()
{
    try {
        do ..%KillExtent()
        for json = {"FirstName":"Magnus", "LastName":"Guvenal"},
        ["Magnus", "Guvenal"],
        {
            "a":{"b":{"c":"Magnus"
            } }} {
            set inst = ..%New()
            set inst.JSON = json.%ToJSON()
            $$$ThrowOnError(inst.%Save())
        }
        do ..DisplaySQL("select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'Magnus')")
        do ..DisplaySQL("select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'Guvenal')")
        do ..DisplaySQL("select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'FirstName')")
    } catch e {
        set sc = e.AsStatus()
        write !,$system.Status.GetErrorText(sc)
    }
}

ClassMethod DisplaySQL(query, args...)
{
    write !,query,!
    for i=1:1:$get(args) {
        write "argument: ",args(i),!
    }
    do ##class(%SQL.Statement).%ExecDirect(,query,args...).%Display()
}

Storage Default
{
<Data name="IndexJSONDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>JSON</Value>
</Value>
</Data>
<DataLocation>^DC.Demo.IndexJSOND</DataLocation>
<DefaultData>IndexJSONDefaultData</DefaultData>
<IdLocation>^DC.Demo.IndexJSOND</IdLocation>
<IndexLocation>^DC.Demo.IndexJSONI</IndexLocation>
<StreamLocation>^DC.Demo.IndexJSONS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

In action:

USER>d ##class(DC.Demo.IndexJSON).Run()
 
select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'Magnus')
JSON
{"FirstName":"Magnus","LastName":"Guvenal"}
["Magnus","Guvenal"]
{"a":{"b":{"c":"Magnus"}}}
 
3 Rows(s) Affected
select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'Guvenal')
JSON
{"FirstName":"Magnus","LastName":"Guvenal"}
["Magnus","Guvenal"]
 
2 Rows(s) Affected
select JSON from DC_Demo.IndexJSON where for some %ELEMENT(JSON) (%KEY = 'FirstName')
JSON
 
0 Rows(s) Affected

Hi Erica,

$$$Text generates content into the message globals at compile time. Here's one way to solve the problem:

Class Erica.DemoLocalizedXData
{

Parameter DOMAIN = "Demo";

XData LocalizedEmail [ MimeType = text/html ]
{
<body>
<p>
Text to be translated into another language
</p>
</body>
}

ClassMethod GetLocalizedContent(xDataName As %String) As %String [ CodeMode = objectgenerator ]
{
    do %code.WriteLine(" Quit $Case(xDataName,")
    set key = ""
    for {
        set xdata = %class.XDatas.GetNext(.key)
        quit:key=""
        set data = xdata.Data.Read() // Assumptions about length here...
        do %code.WriteLine("   "_$$$QUOTE(xdata.Name)_":$$$Text("_$$Quote^%qcr(data)_"),")
    }
    do %code.WriteLine("   :"""")")
}

}

After compilation you'll have:

^IRIS.Msg("Demo")="en"
^IRIS.Msg("Demo","en",3630108798)="<body>"_$c(13,10)_"<p>"_$c(13,10)_"Text to be translated into another language"_$c(13,10)_"</p>"_$c(13,10)_"</body>"_$c(13,10)

If you want to localize individual strings in the XData block independent of the HTML markup that gets a little more complicated. I'd think it's simpler/possibly better to localize the entire block at once though.