I've got this idea of how to solve localization in a play app.
I would have a class that would simply be a wrapper around a Map[String, String] where the map key would be the Locale such as "en-US"
and by implicitly passing the play.api.i18n.Lang I could get a value from the map.
Since alot of my model fields would be translatable, it would be a pain having to duplicate the logic for getting the values from the map. So my though was if I could get salat to deserialize a Map[String, String] into my custom class LocalizedString.
Would it be possible to create a custom serializer, deserializer for LocalizedString?
/** * Used to store a map of lang code -> translation * {{{ * case class MyModel( * name: LocalizedString, * description: LocalizedString * ) * * val myModel = MyModel( * name = LocalizedString("the name in my current locale"), * description = LocalizedString("en" -> "An English description", "sv" -> "En Svensk beskrivning") * ) * * myModel.name = the name in my current locale * * myModel.name("default name") = if the LocalizedString doesn't contain the Lang it will use "default name" * * // We could also have the default message in a lang file * import play.api.i18n.Messages * myModel.name(Messages("name.default")) * * // And since the LocalizedString is also a map we have all the goodies from the Map API * myModel.description.map((code, desc) => println("Description in: " + code + ": " + desc)) * * }}} * @param self */ class LocalizedString(val self: Map[String, String]) extends MapProxy[String, String] {
// Get localized string or fallback to if there is a string at all or fallback even further to an the default argument specified. def apply(default: String = "")(implicit lang: Lang): String = self.get(lang.code).getOrElse(self.headOption.map(_._2).getOrElse(default))
// Get localized string or fallback to if there is a string at all and fallback even further to an empty string. def toString()(implicit lang: Lang): String = apply()(lang)
On Tuesday, November 6, 2012 1:19:20 PM UTC+1, Leon Radley wrote:
> Hey!
> I've got this idea of how to solve localization in a play app.
> I would have a class that would simply be a wrapper around a Map[String, > String] > where the map key would be the Locale such as "en-US"
> and by implicitly passing the play.api.i18n.Lang I could get a value from > the map.
> Since alot of my model fields would be translatable, it would be a pain > having to duplicate the logic for getting the values from the map. > So my though was if I could get salat to deserialize a Map[String, String] > into my custom class LocalizedString.
> Would it be possible to create a custom serializer, deserializer for > LocalizedString?
I saw your question yesterday but unfortunately I am trying to fix a chart
for a demo in six hours.
Salat's collection support is explictly limited to those listed on the
wiki. It can't understand custom subtypes of map. I could add probably
add MapProxy support though.... I'd need certain methods to be implemented
to avoid the infinite stacktraces that result from unimplemented methods in
MapProxy (if you've ever hit one you'd know what I'm talking about)....
Collection support is fairly straightforward, so if you want to work
backward from the collection support spec, you could probably figure out
MapProxy support yourself. The trick is, you'd have to take the path, get
the class, walk the interfaces, and see if it is an instance of MapProxy.
I recently added BitSet support, which hits all the same places you'd need
to go.
Alas, there isn't any support for custom transformers yet.
Best,
Rose
On 7 November 2012 02:35, Leon Radley <l...@radley.se> wrote:
> /**
> * Used to store a map of lang code -> translation
> * {{{
> * case class MyModel(
> * name: LocalizedString,
> * description: LocalizedString
> * )
> *
> * val myModel = MyModel(
> * name = LocalizedString("the name in my current locale"),
> * description = LocalizedString("en" -> "An English description", "sv"
> -> "En Svensk beskrivning")
> * )
> *
> * myModel.name = the name in my current locale
> *
> * myModel.name("default name") = if the LocalizedString doesn't contain
> the Lang it will use "default name"
> *
> * // We could also have the default message in a lang file
> * import play.api.i18n.Messages
> * myModel.name(Messages("name.default"))
> *
> * // And since the LocalizedString is also a map we have all the
> goodies from the Map API
> * myModel.description.map((code, desc) => println("Description in: " +
> code + ": " + desc))
> *
> * }}}
> * @param self
> */
> class LocalizedString(val self: Map[String, String]) extends
> MapProxy[String, String] {
> // Get localized string or fallback to if there is a string at all or
> fallback even further to an the default argument specified.
> def apply(default: String = "")(implicit lang: Lang): String =
> self.get(lang.code).getOrElse(self.headOption.map(_._2).getOrElse(default))
> // Get localized string or fallback to if there is a string at all and
> fallback even further to an empty string.
> def toString()(implicit lang: Lang): String = apply()(lang)
> }
> ---------- CONSTRUCTOR EXPECTS FOR PARAM [1] --------------
> NAME: name
> TYPE: models.LocalizedString
> DEFAULT ARG [Missing, but unnecessary because input value was supplied]
> @Ignore false
> ---------- CONSTRUCTOR INPUT ------------------------
> TYPE: com.mongodb.BasicDBObject
> VALUE:
> { "en" : "English string" , "sv" : "Swedish string"}
> ------------------------------------------------------------
> So my question is, can i force salat to use my LocalizedString or get it
> to understand subtypes of Map?
> On Tuesday, November 6, 2012 1:19:20 PM UTC+1, Leon Radley wrote:
>> Hey!
>> I've got this idea of how to solve localization in a play app.
>> I would have a class that would simply be a wrapper around a Map[String,
>> String]
>> where the map key would be the Locale such as "en-US"
>> and by implicitly passing the play.api.i18n.Lang I could get a value from
>> the map.
>> Since alot of my model fields would be translatable, it would be a pain
>> having to duplicate the logic for getting the values from the map.
>> So my though was if I could get salat to deserialize a Map[String,
>> String] into my custom class LocalizedString.
>> Would it be possible to create a custom serializer, deserializer for
>> LocalizedString?
Since LocalizedString could be implicitly converted to and from a map, where would I import my conversions so that salat (the grater?) would use the implicit conversions? or wouldn't that work?
On Wednesday, November 7, 2012 9:38:01 AM UTC+1, rktoomey wrote:
> Hi Leon,
> I saw your question yesterday but unfortunately I am trying to fix a chart > for a demo in six hours.
> Salat's collection support is explictly limited to those listed on the > wiki. It can't understand custom subtypes of map. I could add probably > add MapProxy support though.... I'd need certain methods to be implemented > to avoid the infinite stacktraces that result from unimplemented methods in > MapProxy (if you've ever hit one you'd know what I'm talking about)....
> Collection support is fairly straightforward, so if you want to work > backward from the collection support spec, you could probably figure out > MapProxy support yourself. The trick is, you'd have to take the path, get > the class, walk the interfaces, and see if it is an instance of MapProxy. > I recently added BitSet support, which hits all the same places you'd need > to go.
> Alas, there isn't any support for custom transformers yet.
> Best, > Rose
> On 7 November 2012 02:35, Leon Radley <le...@radley.se <javascript:>>wrote:
>> Some more info after yesterdays trial and errors...
>> /** >> * Used to store a map of lang code -> translation >> * {{{ >> * case class MyModel( >> * name: LocalizedString, >> * description: LocalizedString >> * ) >> * >> * val myModel = MyModel( >> * name = LocalizedString("the name in my current locale"), >> * description = LocalizedString("en" -> "An English description", >> "sv" -> "En Svensk beskrivning") >> * ) >> * >> * myModel.name = the name in my current locale >> * >> * myModel.name("default name") = if the LocalizedString doesn't >> contain the Lang it will use "default name" >> * >> * // We could also have the default message in a lang file >> * import play.api.i18n.Messages >> * myModel.name(Messages("name.default")) >> * >> * // And since the LocalizedString is also a map we have all the >> goodies from the Map API >> * myModel.description.map((code, desc) => println("Description in: " + >> code + ": " + desc)) >> * >> * }}} >> * @param self >> */ >> class LocalizedString(val self: Map[String, String]) extends >> MapProxy[String, String] {
>> // Get localized string or fallback to if there is a string at all or >> fallback even further to an the default argument specified. >> def apply(default: String = "")(implicit lang: Lang): String = >> self.get(lang.code).getOrElse(self.headOption.map(_._2).getOrElse(default))
>> // Get localized string or fallback to if there is a string at all and >> fallback even further to an empty string. >> def toString()(implicit lang: Lang): String = apply()(lang) >> }
>> ---------- CONSTRUCTOR EXPECTS FOR PARAM [1] -------------- >> NAME: name >> TYPE: models.LocalizedString >> DEFAULT ARG [Missing, but unnecessary because input value was supplied] >> @Ignore false >> ---------- CONSTRUCTOR INPUT ------------------------ >> TYPE: com.mongodb.BasicDBObject >> VALUE: >> { "en" : "English string" , "sv" : "Swedish string"} >> ------------------------------------------------------------
>> So my question is, can i force salat to use my LocalizedString or get it >> to understand subtypes of Map?
>> On Tuesday, November 6, 2012 1:19:20 PM UTC+1, Leon Radley wrote:
>>> Hey!
>>> I've got this idea of how to solve localization in a play app.
>>> I would have a class that would simply be a wrapper around a Map[String, >>> String] >>> where the map key would be the Locale such as "en-US"
>>> and by implicitly passing the play.api.i18n.Lang I could get a value >>> from the map.
>>> Since alot of my model fields would be translatable, it would be a pain >>> having to duplicate the logic for getting the values from the map. >>> So my though was if I could get salat to deserialize a Map[String, >>> String] into my custom class LocalizedString.
>>> Would it be possible to create a custom serializer, deserializer for >>> LocalizedString?
MapProxy implements Map which means that LocalizedString is a Map But since we simply look if the class string ends with "Map" it won't detect the subtyping.
How can we improve this?
Is it possible to adopt the same functionality that Play uses for it's JSON lib.
This could mean that to extend salat could be as simple as implementing a Read and a Write and exposing the implicit variables to the grater via the context.
On Wednesday, November 7, 2012 9:38:01 AM UTC+1, rktoomey wrote:
> Hi Leon,
> I saw your question yesterday but unfortunately I am trying to fix a chart > for a demo in six hours.
> Salat's collection support is explictly limited to those listed on the > wiki. It can't understand custom subtypes of map. I could add probably > add MapProxy support though.... I'd need certain methods to be implemented > to avoid the infinite stacktraces that result from unimplemented methods in > MapProxy (if you've ever hit one you'd know what I'm talking about)....
> Collection support is fairly straightforward, so if you want to work > backward from the collection support spec, you could probably figure out > MapProxy support yourself. The trick is, you'd have to take the path, get > the class, walk the interfaces, and see if it is an instance of MapProxy. > I recently added BitSet support, which hits all the same places you'd need > to go.
> Alas, there isn't any support for custom transformers yet.
> Best, > Rose
> On 7 November 2012 02:35, Leon Radley <le...@radley.se <javascript:>>wrote:
>> Some more info after yesterdays trial and errors...
>> /** >> * Used to store a map of lang code -> translation >> * {{{ >> * case class MyModel( >> * name: LocalizedString, >> * description: LocalizedString >> * ) >> * >> * val myModel = MyModel( >> * name = LocalizedString("the name in my current locale"), >> * description = LocalizedString("en" -> "An English description", >> "sv" -> "En Svensk beskrivning") >> * ) >> * >> * myModel.name = the name in my current locale >> * >> * myModel.name("default name") = if the LocalizedString doesn't >> contain the Lang it will use "default name" >> * >> * // We could also have the default message in a lang file >> * import play.api.i18n.Messages >> * myModel.name(Messages("name.default")) >> * >> * // And since the LocalizedString is also a map we have all the >> goodies from the Map API >> * myModel.description.map((code, desc) => println("Description in: " + >> code + ": " + desc)) >> * >> * }}} >> * @param self >> */ >> class LocalizedString(val self: Map[String, String]) extends >> MapProxy[String, String] {
>> // Get localized string or fallback to if there is a string at all or >> fallback even further to an the default argument specified. >> def apply(default: String = "")(implicit lang: Lang): String = >> self.get(lang.code).getOrElse(self.headOption.map(_._2).getOrElse(default))
>> // Get localized string or fallback to if there is a string at all and >> fallback even further to an empty string. >> def toString()(implicit lang: Lang): String = apply()(lang) >> }
>> ---------- CONSTRUCTOR EXPECTS FOR PARAM [1] -------------- >> NAME: name >> TYPE: models.LocalizedString >> DEFAULT ARG [Missing, but unnecessary because input value was supplied] >> @Ignore false >> ---------- CONSTRUCTOR INPUT ------------------------ >> TYPE: com.mongodb.BasicDBObject >> VALUE: >> { "en" : "English string" , "sv" : "Swedish string"} >> ------------------------------------------------------------
>> So my question is, can i force salat to use my LocalizedString or get it >> to understand subtypes of Map?
>> On Tuesday, November 6, 2012 1:19:20 PM UTC+1, Leon Radley wrote:
>>> Hey!
>>> I've got this idea of how to solve localization in a play app.
>>> I would have a class that would simply be a wrapper around a Map[String, >>> String] >>> where the map key would be the Locale such as "en-US"
>>> and by implicitly passing the play.api.i18n.Lang I could get a value >>> from the map.
>>> Since alot of my model fields would be translatable, it would be a pain >>> having to duplicate the logic for getting the values from the map. >>> So my though was if I could get salat to deserialize a Map[String, >>> String] into my custom class LocalizedString.
>>> Would it be possible to create a custom serializer, deserializer for >>> LocalizedString?
Actually, Salat uses the class names from the pickled scala sigs. So you'd
need detailed understanding of what the TypeRefType for LocalizedString
looks like, so that you could generically figure out where the details you
need to actually deserialize each part correctly are located.
In the case of MapProxy, your issue would be that the existing map matcher
is too greedy.
But now I've had a night of sleep, I think you can do this more simply -
you could use this example to create a cursor where you take the raw
DBObject, deserialize LocalizedString yourself, and then pass it onward to
the grater for the top level object:
https://gist.github.com/3425185
That should get things working for the time being.
I'll look at the Play docs. I don't know if I'll take that approach
though. Implicits are great until your code reaches a point where
manifests and implicits and view bounds all collide, making the unsugared
syntax just miserable to work with (look at some of the stuff in SalatDAO).
Best,
Rose
On 8 November 2012 10:06, Leon Radley <l...@radley.se> wrote:
> MapProxy implements Map which means that LocalizedString is a Map
> But since we simply look if the class string ends with "Map" it won't
> detect the subtyping.
> How can we improve this?
> Is it possible to adopt the same functionality that Play uses for it's
> JSON lib.
> This could mean that to extend salat could be as simple as implementing a
> Read and a Write and exposing the implicit variables to the grater via the
> context.
> What do you think?
> On Wednesday, November 7, 2012 9:38:01 AM UTC+1, rktoomey wrote:
>> Hi Leon,
>> I saw your question yesterday but unfortunately I am trying to fix a
>> chart for a demo in six hours.
>> Salat's collection support is explictly limited to those listed on the
>> wiki. It can't understand custom subtypes of map. I could add probably
>> add MapProxy support though.... I'd need certain methods to be implemented
>> to avoid the infinite stacktraces that result from unimplemented methods in
>> MapProxy (if you've ever hit one you'd know what I'm talking about)....
>> Collection support is fairly straightforward, so if you want to work
>> backward from the collection support spec, you could probably figure out
>> MapProxy support yourself. The trick is, you'd have to take the path, get
>> the class, walk the interfaces, and see if it is an instance of MapProxy.
>> I recently added BitSet support, which hits all the same places you'd need
>> to go.
>> Alas, there isn't any support for custom transformers yet.
>> Best,
>> Rose
>> On 7 November 2012 02:35, Leon Radley <le...@radley.se> wrote:
>>> Some more info after yesterdays trial and errors...
>>> I've come up with a model that looks like this
>>> /**
>>> * Used to store a map of lang code -> translation
>>> * {{{
>>> * case class MyModel(
>>> * name: LocalizedString,
>>> * description: LocalizedString
>>> * )
>>> *
>>> * val myModel = MyModel(
>>> * name = LocalizedString("the name in my current locale"),
>>> * description = LocalizedString("en" -> "An English description",
>>> "sv" -> "En Svensk beskrivning")
>>> * )
>>> *
>>> * myModel.name = the name in my current locale
>>> *
>>> * myModel.name("default name") = if the LocalizedString doesn't
>>> contain the Lang it will use "default name"
>>> *
>>> * // We could also have the default message in a lang file
>>> * import play.api.i18n.Messages
>>> * myModel.name(Messages("name.**default"))
>>> *
>>> * // And since the LocalizedString is also a map we have all the
>>> goodies from the Map API
>>> * myModel.description.map((code, desc) => println("Description in: "
>>> + code + ": " + desc))
>>> *
>>> * }}}
>>> * @param self
>>> */
>>> class LocalizedString(val self: Map[String, String]) extends
>>> MapProxy[String, String] {
>>> // Get localized string or fallback to if there is a string at all or
>>> fallback even further to an the default argument specified.
>>> def apply(default: String = "")(implicit lang: Lang): String =
>>> self.get(lang.code).getOrElse(**self.headOption.map(_._2).**
>>> getOrElse(default))
>>> // Get localized string or fallback to if there is a string at all and
>>> fallback even further to an empty string.
>>> def toString()(implicit lang: Lang): String = apply()(lang)
>>> }
>>> ---------- CONSTRUCTOR EXPECTS FOR PARAM [1] --------------
>>> NAME: name
>>> TYPE: models.LocalizedString
>>> DEFAULT ARG [Missing, but unnecessary because input value was supplied]
>>> @Ignore false
>>> ---------- CONSTRUCTOR INPUT ------------------------
>>> TYPE: com.mongodb.BasicDBObject
>>> VALUE:
>>> { "en" : "English string" , "sv" : "Swedish string"}
>>> ------------------------------**------------------------------
>>> So my question is, can i force salat to use my LocalizedString or get it
>>> to understand subtypes of Map?
>>> On Tuesday, November 6, 2012 1:19:20 PM UTC+1, Leon Radley wrote:
>>>> Hey!
>>>> I've got this idea of how to solve localization in a play app.
>>>> I would have a class that would simply be a wrapper around a
>>>> Map[String, String]
>>>> where the map key would be the Locale such as "en-US"
>>>> and by implicitly passing the play.api.i18n.Lang I could get a value
>>>> from the map.
>>>> Since alot of my model fields would be translatable, it would be a pain
>>>> having to duplicate the logic for getting the values from the map.
>>>> So my though was if I could get salat to deserialize a Map[String,
>>>> String] into my custom class LocalizedString.
>>>> Would it be possible to create a custom serializer, deserializer for
>>>> LocalizedString?